├── www ├── res │ ├── bg.jpg │ ├── overlay-button.png │ ├── reset.css │ ├── style.css │ └── form.js ├── index.php └── .htaccess ├── templates ├── projects │ ├── wrapper.tpl.php │ ├── delete.tpl.php │ ├── edit.tpl.php │ ├── new.tpl.php │ ├── list.tpl.php │ ├── show.tpl.php │ └── form.tpl.php ├── login.tpl.php ├── releases │ ├── create.tpl.php │ └── list.tpl.php ├── root.tpl.php ├── document.tpl.php └── faq.tpl.php ├── config ├── pirum.xml.dist ├── development.inc.php └── global.inc.php ├── .gitmodules ├── script ├── migrations │ ├── 20100103203704.php │ ├── 20100103174435.php │ ├── 20100110180743.php │ ├── 20100106231809.php │ ├── 20100110222512.php │ ├── 20100102225241.php │ ├── 20100105235825.php │ ├── 20100106231514.php │ ├── 20100124151547.php │ ├── 20100104235349.php │ ├── 20091130225642.php │ ├── 20091226214859.php │ ├── 20091227215606.php │ ├── 20100103155123.php │ ├── 20100124143444.php │ └── 20091114214330.php ├── generate_model ├── generate_components ├── generate_migration ├── install ├── migrate ├── generate_test_functional ├── test_all └── tasks ├── .gitignore ├── lib ├── document.php ├── components │ ├── faq.php │ ├── logout.php │ ├── root.php │ ├── login.php │ ├── releases.php │ └── projects │ │ ├── list.php │ │ └── entry.php ├── applicationfactory.php ├── shell.inc.php ├── openid.inc.php ├── viewhelpers.inc.php ├── builder.inc.php ├── repo.inc.php └── projects.inc.php ├── README └── test ├── builder.php └── functional └── root.test.php /www/res/bg.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/offers/pearhub/HEAD/www/res/bg.jpg -------------------------------------------------------------------------------- /www/res/overlay-button.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/offers/pearhub/HEAD/www/res/overlay-button.png -------------------------------------------------------------------------------- /templates/projects/wrapper.tpl.php: -------------------------------------------------------------------------------- 1 |

Projects

2 |

3 | Back to index 4 |

5 | 8 | 9 | -------------------------------------------------------------------------------- /config/pirum.xml.dist: -------------------------------------------------------------------------------- 1 | 2 | 3 | pearhub.org 4 | pearhub PEAR channel 5 | pearhub 6 | http://pearhub.org/ 7 | 8 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "thirdparty/bucket"] 2 | path = thirdparty/bucket 3 | url = git://github.com/troelskn/bucket.git 4 | [submodule "thirdparty/konstrukt"] 5 | path = thirdparty/konstrukt 6 | url = git://github.com/troelskn/konstrukt.git 7 | -------------------------------------------------------------------------------- /script/migrations/20100103203704.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | create('PDO'); 6 | $db->exec('alter table releases drop column report'); 7 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | log/ 2 | var/ 3 | tmp/ 4 | thirdparty/pdoext/ 5 | thirdparty/zf/ 6 | thirdparty/pirum 7 | www/get 8 | www/rest 9 | www/channel.xml 10 | www/feed.xml 11 | /.buildpath 12 | /.project 13 | config/local.inc.php 14 | config/pirum.xml 15 | *.iml 16 | .idea/* 17 | *.swp 18 | -------------------------------------------------------------------------------- /lib/document.php: -------------------------------------------------------------------------------- 1 | crumbtrail; 7 | } 8 | function addCrumb($title, $url) { 9 | return $this->crumbtrail[] = array('title' => $title, 'url' => $url); 10 | } 11 | } -------------------------------------------------------------------------------- /templates/projects/delete.tpl.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |

4 | Delete the project? 5 |

6 | 7 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /script/migrations/20100103174435.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | create('PDO'); 6 | $db->exec( 7 | ' 8 | alter table releases add column report text default NULL after status 9 | ' 10 | ); 11 | 12 | -------------------------------------------------------------------------------- /script/migrations/20100110180743.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | create('PDO'); 6 | $db->exec( 7 | ' 8 | alter table releases add column fail_reason text default NULL after status 9 | ' 10 | ); 11 | -------------------------------------------------------------------------------- /script/migrations/20100106231809.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | create('PDO'); 6 | $db->exec( 7 | ' 8 | alter table projects add column latest_version varchar(20) default NULL after created 9 | ' 10 | ); -------------------------------------------------------------------------------- /script/migrations/20100110222512.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | create('PDO'); 6 | $db->exec( 7 | ' 8 | alter table repository_types add column last_probe datetime default NULL after type 9 | ' 10 | ); -------------------------------------------------------------------------------- /script/migrations/20100102225241.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | create('PDO'); 6 | $db->exec( 7 | ' 8 | alter table projects add column release_policy enum("manual", "auto") not null default "auto" after php_version 9 | ' 10 | ); 11 | 12 | -------------------------------------------------------------------------------- /script/generate_model: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | run(); 11 | 12 | -------------------------------------------------------------------------------- /script/generate_components: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | run(); 11 | -------------------------------------------------------------------------------- /script/migrations/20100105235825.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | create('PDO'); 6 | $db->exec( 7 | ' 8 | create table repository_types ( 9 | url varchar(255) not null, 10 | type varchar(20) not null, 11 | primary key (url, type), 12 | index (url, type) 13 | ) 14 | ' 15 | ); 16 | -------------------------------------------------------------------------------- /script/migrations/20100106231514.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | create('PDO'); 6 | $db->exec('drop table repository_types'); 7 | $db->exec( 8 | ' 9 | create table repository_types ( 10 | url varchar(255) not null, 11 | type varchar(20) not null, 12 | primary key (url), 13 | index (url) 14 | ) 15 | ' 16 | ); 17 | -------------------------------------------------------------------------------- /script/migrations/20100124151547.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | create('PDO'); 6 | $db->exec( 7 | ' 8 | alter table projects add column repository_username varchar(255) default NULL 9 | ' 10 | ); 11 | $db->exec( 12 | ' 13 | alter table projects add column repository_password varchar(255) default NULL 14 | ' 15 | ); 16 | -------------------------------------------------------------------------------- /script/migrations/20100104235349.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | create('PDO'); 6 | $db->exec( 7 | ' 8 | alter table projects add column description text default NULL after summary 9 | ' 10 | ); 11 | $db->exec( 12 | ' 13 | alter table projects change column summary summary varchar(200) default NULL 14 | ' 15 | ); 16 | 17 | 18 | -------------------------------------------------------------------------------- /lib/components/faq.php: -------------------------------------------------------------------------------- 1 | templates = $templates; 6 | } 7 | function dispatch() { 8 | $this->document->addCrumb('FAQ', $this->url()); 9 | return parent::dispatch(); 10 | } 11 | function renderHtml() { 12 | $this->document->setTitle('FAQ'); 13 | $t = $this->templates->create("faq"); 14 | return $t->render($this); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /script/migrations/20091130225642.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | create('PDO'); 6 | $db->exec('drop table if exists authentication_cookies'); 7 | $db->exec('create table authentication_cookies ( 8 | openid_identifier varchar(255) not null primary key, 9 | hash varchar(255) not null, 10 | user_agent varchar(255) not null, 11 | created datetime not null, 12 | unique(hash), 13 | index(hash) 14 | ) 15 | '); 16 | -------------------------------------------------------------------------------- /templates/projects/edit.tpl.php: -------------------------------------------------------------------------------- 1 |

2 | 3 | canDelete()): ?> 4 | | 5 | 6 |

7 | 'project-form')); ?> 8 | 9 | 10 | 14 | 15 | -------------------------------------------------------------------------------- /script/migrations/20091226214859.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | create('PDO'); 6 | $db->exec('drop table if exists dependencies'); 7 | $db->exec(' 8 | create table dependencies ( 9 | project_id bigint not null, 10 | channel varchar(255) not null, 11 | version varchar(64), 12 | primary key (project_id, channel), 13 | index (project_id), 14 | foreign key (project_id) references projects(id) 15 | ) 16 | 17 | '); 18 | 19 | -------------------------------------------------------------------------------- /templates/projects/new.tpl.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |
4 | 5 | name(), array('id' => "field-name")); ?> 6 | 7 |
8 | 9 | 10 | 11 | 15 | 16 | 17 | -------------------------------------------------------------------------------- /www/index.php: -------------------------------------------------------------------------------- 1 | setComponentCreator(new k_InjectorAdapter($container, new Document())) 8 | ->setIdentityLoader($container->get('k_IdentityLoader')) 9 | // Location of debug logging 10 | ->setLog($debug_log_path) 11 | // Enable/disable in-browser debugging 12 | ->setDebug($debug_enabled) 13 | // Dispatch request 14 | ->run('components_Root') 15 | ->out(); 16 | -------------------------------------------------------------------------------- /script/generate_migration: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | create(\'PDO\'); 13 | '; 14 | 15 | echo "Writing file '$base_name'.\n"; 16 | file_put_contents($destination_file_name, $php); 17 | chmod($destination_file_name, 0755); -------------------------------------------------------------------------------- /script/migrations/20091227215606.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | create('PDO'); 6 | $db->exec('drop table if exists file_ignores'); 7 | $db->exec('drop table if exists filespecs'); 8 | $db->exec('drop table if exists files'); 9 | $db->exec( 10 | ' 11 | create table files ( 12 | project_id bigint not null, 13 | path varchar(255) not null, 14 | destination varchar(255) not null default "/", 15 | `ignore` varchar(255), 16 | primary key (project_id, path), 17 | index (project_id), 18 | foreign key (project_id) references projects(id) 19 | ) 20 | ' 21 | ); 22 | -------------------------------------------------------------------------------- /script/migrations/20100103155123.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | create('PDO'); 6 | $db->exec('drop table if exists releases'); 7 | $db->exec(' 8 | create table releases ( 9 | project_id bigint not null, 10 | version varchar(64) not null, 11 | status enum("building","completed","failed") not null default "building", 12 | created datetime not null, 13 | mode enum("auto","manual") not null default "auto", 14 | primary key (project_id, version), 15 | index (project_id), 16 | index (version), 17 | foreign key (project_id) references projects(id) 18 | ) 19 | '); 20 | -------------------------------------------------------------------------------- /templates/login.tpl.php: -------------------------------------------------------------------------------- 1 | 'openid')); ?> 2 |

OpenID Login

3 |

4 | ", array_map('escape', $errors)) ?> 5 |

6 |
7 | 12 |
13 | 14 |
15 |

Don't have an OpenID login?

16 |

17 | You can create a login for free at: myopenid.com 18 |

19 | 20 | -------------------------------------------------------------------------------- /templates/releases/create.tpl.php: -------------------------------------------------------------------------------- 1 | 2 | 3 |

4 | 5 |

6 | 7 |

8 | Create a new release for displayName()) ?> from the repository at: 9 |

10 |

11 | repository()) ?> 12 |

13 | 14 |

15 | 16 | 'field-version')); ?> 17 |

18 | 19 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /README: -------------------------------------------------------------------------------- 1 | This is the source code for the pearhub.org website, where you can register pear packages and have them published. 2 | 3 | Install 4 | --- 5 | 6 | Create a database, and make a copy of `config/development.inc.php` to `config/local.inc.php`. Then edit it. 7 | Copy `config/pirum.xml.dist` to `config/pirum.xml` and edit it too. 8 | 9 | Run `script/install` 10 | 11 | You need to have a couple of dependencies installed on your system, such as svn and git. 12 | 13 | You also need to install a cronjob to run the following tasks periodically: 14 | 15 | script/tasks sweep_projects 16 | script/tasks build_pending_releases 17 | 18 | You might install them to run every 5 minutes or so, in sequence. 19 | 20 | --- 21 | 22 | All code is copyright by Troels Knak-Nielsen, 2010, and is hereby freely available under the BSD license. 23 | -------------------------------------------------------------------------------- /www/.htaccess: -------------------------------------------------------------------------------- 1 | # Apache configuration file for Konstrukt application 2 | 3 | # These lines enables logging of errors to /log/error.log 4 | php_value error_log "../log/error.log" 5 | php_value log_errors on 6 | php_value html_errors off 7 | 8 | # Enable display errors in browser 9 | # For a production site, set the following two lines to off 10 | php_value display_startup_errors on 11 | php_value display_errors on 12 | 13 | # These settings routes all traffic, except concrete files, to the dispatcher 14 | DirectorySlash Off 15 | Options FollowSymLinks Indexes 16 | DirectoryIndex index.php 17 | 18 | RewriteEngine on 19 | 20 | RewriteCond %{REQUEST_FILENAME} -d 21 | RewriteRule ^.*$ - [L] 22 | 23 | RewriteCond %{REQUEST_FILENAME} -f 24 | RewriteRule ^.*$ - [L] 25 | 26 | RewriteRule ^.*$ index.php [L] 27 | -------------------------------------------------------------------------------- /www/res/reset.css: -------------------------------------------------------------------------------- 1 | /* 2 | Copyright (c) 2008, Yahoo! Inc. All rights reserved. 3 | Code licensed under the BSD License: 4 | http://developer.yahoo.net/yui/license.txt 5 | version: 2.5.2 6 | */ 7 | html{color:#000;background:#FFF;}body,div,dl,dt,dd,ul,ol,li,h1,h2,h3,h4,h5,h6,pre,code,form,fieldset,legend,input,textarea,p,blockquote,th,td{margin:0;padding:0;}table{border-collapse:collapse;border-spacing:0;}fieldset,img{border:0;}address,caption,cite,code,dfn,em,strong,th,var{font-style:normal;font-weight:normal;}li{list-style:none;}caption,th{text-align:left;}h1,h2,h3,h4,h5,h6{font-size:100%;font-weight:normal;}q:before,q:after{content:'';}abbr,acronym {border:0;font-variant:normal;}sup {vertical-align:text-top;}sub {vertical-align:text-bottom;}input,textarea,select{font-family:inherit;font-size:inherit;font-weight:inherit;}input,textarea,select{*font-size:100%;}legend{color:#000;} 8 | -------------------------------------------------------------------------------- /script/install: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # Pull thirdparty libraries 4 | git submodule init 5 | git submodule update 6 | svn export http://pdoext.googlecode.com/svn/trunk thirdparty/pdoext 7 | svn export http://framework.zend.com/svn/framework/standard/tags/release-1.9.5/library/ thirdparty/zf 8 | pushd thirdparty || exit -1 9 | wget http://github.com/troelskn/Pirum/raw/master/pirum 10 | chmod +x pirum 11 | popd 12 | 13 | # Set file permissions 14 | chmod 777 log 15 | 16 | # create var/channel 17 | pushd www || exit -1 18 | ln -s ../var/channel/get get 19 | ln -s ../var/channel/rest rest 20 | ln -s ../var/channel/feed.xml feed.xml 21 | ln -s ../var/channel/channel.xml channel.xml 22 | popd 23 | mkdir -p var/channel && chmod 777 var/channel 24 | pushd var/channel || exit -1 25 | ln -s ../../config/pirum.xml pirum.xml 26 | popd 27 | 28 | script/migrate 29 | script/tasks pirum_build_channel 30 | -------------------------------------------------------------------------------- /templates/projects/list.tpl.php: -------------------------------------------------------------------------------- 1 | 8 | 9 | 18 | 19 | 20 | 21 | canCreate()): ?> 22 |

23 | Add new project 24 |

25 | 26 | -------------------------------------------------------------------------------- /lib/components/logout.php: -------------------------------------------------------------------------------- 1 | zend_auth = $zend_auth; 9 | $this->auth_cookies = $auth_cookies; 10 | } 11 | function execute() { 12 | $this->url_state->init("continue", $this->url('/')); 13 | return parent::execute(); 14 | } 15 | function postForm() { 16 | if ($this->zend_auth->hasIdentity()) { 17 | $this->zend_auth->clearIdentity(); 18 | } 19 | $this->session()->set('identity', null); 20 | if ($this->cookie('user')) { 21 | $this->auth_cookies->delete(array('hash' => $this->cookie('user'))); 22 | $this->cookie()->set('user', null); 23 | } 24 | return new k_SeeOther($this->query('continue')); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /script/migrations/20100124143444.php: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | create('PDO'); 6 | $db->exec( 7 | ' 8 | alter table projects add column path varchar(255) NOT NULL 9 | ' 10 | ); 11 | $db->exec( 12 | ' 13 | alter table projects add column destination varchar(255) NOT NULL default "/" 14 | ' 15 | ); 16 | $db->exec( 17 | ' 18 | alter table projects add column `ignore` varchar(255) default NULL 19 | ' 20 | ); 21 | foreach ($db->pexecute('select * from files') as $row) { 22 | $db->pexecute( 23 | 'update projects set path = :path, destination = :destination, `ignore` = :ignore where id = :project_id', 24 | array( 25 | ':path' => $row['path'], 26 | ':destination' => $row['destination'], 27 | ':ignore' => $row['ignore'], 28 | ':project_id' => $row['project_id'])); 29 | } 30 | $db->exec('drop table files'); 31 | -------------------------------------------------------------------------------- /test/builder.php: -------------------------------------------------------------------------------- 1 | get('PDO'); 9 | $gateway = new ProjectGateway($db, new MaintainerGateway($db)); 10 | $project = $gateway->fetch(array('name' => 'konstrukt')); 11 | $sh = new Shell(); 12 | $sh->temp_dir = $tmp_path; 13 | $repo = new SvnStandardRepoInfo($project->repository(), $sh); 14 | $tags = $repo->listTags(); 15 | $version = $tags[count($tags)-1]; 16 | $local_copy = $repo->exportTag($version); 17 | echo $local_copy, "\n"; 18 | 19 | $compiler = new ManifestCompiler($project); 20 | $files = new FileFinder($local_copy->getPath()); 21 | foreach ($project->files() as $file) { 22 | $files->traverse($file['path'], $file['ignore'], $file['destination']); 23 | } 24 | 25 | file_put_contents( 26 | $local_copy->getPath().'/package.xml', 27 | $compiler->build($files, $project, $version)); 28 | //echo $compiler->build($files, $project, $version); 29 | //$local_copy->destroy($sh); 30 | -------------------------------------------------------------------------------- /test/functional/root.test.php: -------------------------------------------------------------------------------- 1 | container = create_container(); 13 | return new k_VirtualSimpleBrowser('components_Root', new k_InjectorAdapter($this->container, new Document())); 14 | } 15 | function createInvoker() { 16 | return new SimpleInvoker($this); 17 | } 18 | function test_root_prompts_for_login() { 19 | $this->assertTrue($this->get('/')); 20 | $this->assertResponse(200); 21 | $this->assertLink("login"); 22 | } 23 | function test_mock_auth() { 24 | $user = new k_AuthenticatedUser('lorem.ipsum'); 25 | $this->container->get('k_adapter_MockSessionAccess')->set('identity', $user); 26 | $this->assertTrue($this->get('/')); 27 | $this->assertResponse(200); 28 | $this->assertLink("logout"); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /script/migrate: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | read())) { 18 | $fullname = $dir_migrations . DIRECTORY_SEPARATOR . $entry; 19 | if (is_file($fullname) && is_executable($fullname) && !in_array($entry, $logentries)) { 20 | $oustanding[$entry] = $fullname; 21 | } 22 | } 23 | $d->close(); 24 | ksort($oustanding); 25 | if (count($oustanding) == 0) { 26 | echo "Nothing to do.\n"; 27 | } 28 | foreach ($oustanding as $entry => $fullname) { 29 | echo "Running migration: " . $entry ."\n"; 30 | system($fullname, $retval); 31 | if ($retval === 0) { 32 | echo "OK.\n"; 33 | } else { 34 | echo "External command failed with exit code ($retval).\n"; 35 | exit($retval); 36 | } 37 | error_log($entry . "\n", 3, $file_log ); 38 | } 39 | -------------------------------------------------------------------------------- /lib/applicationfactory.php: -------------------------------------------------------------------------------- 1 | pdo_dsn, $this->pdo_username, $this->pdo_password); 17 | if ($this->pdo_log_target) { 18 | $conn->setLogTarget($this->pdo_log_target); 19 | } 20 | return $conn; 21 | } 22 | function new_k_TemplateFactory($c) { 23 | return new k_DefaultTemplateFactory($this->template_dir); 24 | } 25 | function new_Zend_Auth($c) { 26 | return Zend_Auth::getInstance(); 27 | } 28 | function new_Shell($c) { 29 | $shell = new Shell(); 30 | if ($this->temp_dir) { 31 | $shell->temp_dir = $this->temp_dir; 32 | } 33 | return $shell; 34 | } 35 | function new_PackageBuilder($c) { 36 | $builder = new PackageBuilder($c->get('Shell'), $this->package_dir); 37 | $builder->setChannel($this->channel); 38 | return $builder; 39 | } 40 | function new_Pearhub_PirumBuilder($c) { 41 | return new Pearhub_PirumBuilder( 42 | $this->pirum_channel_dir, 43 | new Pirum_CLI_Formatter()); 44 | } 45 | 46 | } 47 | -------------------------------------------------------------------------------- /templates/root.tpl.php: -------------------------------------------------------------------------------- 1 |

2 | Pearhub is a pear channel and a pear package publishing platform. As a user, you can install packages. As a developer, you can publish packages. 3 |

4 | 5 |

6 | 7 | , 8 | , 9 | 10 | 11 |

12 | 13 |

Install

14 |

15 | Packages are installed through the pear installer. Before you can use pearhub, you need to initialize the channel. You only need to do this once: 16 |

17 |
$ pear channel-discover pearhub.org
18 |

19 | Then go and to install. It's as easy as typing: 20 |

21 |
$ pear install pearhub/PackageName
22 | 23 |

Publish

24 | 25 |

Already have a project on github, google code or another public repository? Want to provide an easy way to install and update it?

26 |

and have it appear in the channel in minutes. Promise!

27 | 28 |

¿Confused?

29 | 30 |

Try the - full of made-up slightly-condenscending questions and answers.

31 | -------------------------------------------------------------------------------- /lib/components/root.php: -------------------------------------------------------------------------------- 1 | templates = $templates; 6 | } 7 | protected function map($name) { 8 | switch ($name) { 9 | case 'projects': 10 | return 'components_projects_List'; 11 | case 'login': 12 | return 'components_Login'; 13 | case 'logout': 14 | return 'components_Logout'; 15 | case 'faq': 16 | return 'components_Faq'; 17 | } 18 | } 19 | function dispatch() { 20 | $this->document->addCrumb('pearhub.org', $this->url()); 21 | return parent::dispatch(); 22 | } 23 | function execute() { 24 | return $this->wrap(parent::execute()); 25 | } 26 | function wrapHtml($content) { 27 | $this->document->addStyle($this->url('/res/reset.css')); 28 | $this->document->addStyle($this->url('/res/style.css')); 29 | $t = $this->templates->create("document"); 30 | return 31 | $t->render( 32 | $this, 33 | array( 34 | 'content' => $content, 35 | 'title' => $this->document->title(), 36 | 'crumbtrail' => $this->document->crumbtrail(), 37 | 'scripts' => $this->document->scripts(), 38 | 'styles' => $this->document->styles(), 39 | 'onload' => $this->document->onload())); 40 | } 41 | function renderHtml() { 42 | $this->document->setTitle(''); 43 | $t = $this->templates->create("root"); 44 | return $t->render($this); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /config/development.inc.php: -------------------------------------------------------------------------------- 1 | template_dir = realpath(dirname(__FILE__) . '/../templates'); 19 | $factory->pdo_dsn = 'mysql:host=localhost;dbname=pearhub'; 20 | $factory->pdo_username = 'root'; 21 | $factory->pdo_password = 'password'; 22 | $factory->channel = 'pearhub.org'; 23 | if (isset($GLOBALS['sql_log_path'])) { 24 | $factory->pdo_log_target = $GLOBALS['sql_log_path']; 25 | } 26 | $factory->temp_dir = realpath(dirname(__FILE__) . '/../var/tmp'); 27 | $factory->package_dir = realpath(dirname(__FILE__) . '/../var/channel/get'); 28 | $factory->pirum_channel_dir = dirname(__FILE__).'/../var/channel'; 29 | $container->registerImplementation('PDO', 'pdoext_Connection'); 30 | $container->registerImplementation('k_DefaultNotAuthorizedComponent', 'NotAuthorizedComponent'); 31 | $container->registerImplementation('k_IdentityLoader', 'CookieIdentityLoader'); 32 | return $container; 33 | } 34 | -------------------------------------------------------------------------------- /script/generate_test_functional: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | container = create_container(); 36 | return new k_VirtualSimpleBrowser(\''.$class_name.'\', new k_InjectorAdapter($this->container)); 37 | } 38 | function createInvoker() { 39 | return new SimpleInvoker($this); 40 | } 41 | function test_get_component() { 42 | $this->assertTrue($this->get(\'/\')); 43 | $this->assertResponse(200); 44 | } 45 | } 46 | '; 47 | 48 | echo "Writing file '$destination_file_name'.\n"; 49 | file_put_contents($destination_file_name, $php); 50 | -------------------------------------------------------------------------------- /config/global.inc.php: -------------------------------------------------------------------------------- 1 | create('PDO'); 6 | $db->exec('drop table if exists projects'); 7 | $db->exec('create table projects ( 8 | id serial, 9 | name varchar(255) not null, 10 | owner varchar(255) not null, 11 | created datetime not null, 12 | repository varchar(255) not null, 13 | summary text, 14 | href varchar(255), 15 | license_title varchar(255), 16 | license_href varchar(255), 17 | php_version varchar(10), 18 | index (name), 19 | unique (name) 20 | ) 21 | '); 22 | $db->exec('drop table if exists maintainers'); 23 | $db->exec( 24 | ' 25 | create table maintainers ( 26 | user varchar(64) not null primary key, 27 | name varchar(255), 28 | email varchar(255), 29 | owner varchar(255) not null 30 | ) 31 | ' 32 | ); 33 | $db->exec('drop table if exists project_maintainers'); 34 | $db->exec( 35 | ' 36 | create table project_maintainers ( 37 | project_id bigint not null, 38 | user varchar(64) not null, 39 | type varchar(64) not null, 40 | primary key (project_id, user), 41 | index (project_id), 42 | index (user), 43 | foreign key (project_id) references projects(id), 44 | foreign key (user) references maintainers(user) 45 | ) 46 | ' 47 | ); 48 | $db->exec('drop table if exists filespecs'); 49 | $db->exec( 50 | ' 51 | create table filespecs ( 52 | project_id bigint not null, 53 | path varchar(255) not null, 54 | type enum("src", "doc", "bin") not null default "src", 55 | primary key (project_id, path), 56 | index (project_id), 57 | foreign key (project_id) references projects(id) 58 | ) 59 | ' 60 | ); 61 | $db->exec('drop table if exists file_ignores'); 62 | $db->exec( 63 | ' 64 | create table file_ignores ( 65 | project_id bigint not null, 66 | pattern varchar(255) not null primary key, 67 | index (project_id), 68 | foreign key (project_id) references projects(id) 69 | ) 70 | ' 71 | ); 72 | -------------------------------------------------------------------------------- /templates/releases/list.tpl.php: -------------------------------------------------------------------------------- 1 | releasePolicy() === 'auto'): ?> 2 |

3 | This project is on automatic release policy. 4 | canCreate()): ?> 5 | To roll a new release just make a tag in the repository. Having trouble? Check the . 6 | 7 | New releases will appear here, shortly after they are tagged in the projects repository. 8 | 9 |

10 | 11 |

12 | This project is on manual release policy. 13 | canCreate()): ?> 14 | . 15 | 16 |

17 | 18 | 19 | 20 |

21 | There are no releases yet. 22 |

23 | 24 |

25 | To install a particular version, use: 26 |

27 |
$ pear install pearhub/name()) ?>-X.X.X
28 | 29 | 30 | 31 | 59 | -------------------------------------------------------------------------------- /templates/projects/show.tpl.php: -------------------------------------------------------------------------------- 1 |

2 | canEdit()): ?> 3 | | 4 | 5 | 6 |

7 | 8 | latestVersion()) : ?> 9 |

10 | To install this package run the following from the console: 11 |

12 |
$ pear install pearhub/name()) ?>
13 | 14 |

There are no releases for this project yet.

15 | 16 | 17 |

Summary

18 | 19 |

20 | summary()); ?> 21 |

22 | 23 | href()) : ?> 24 |

25 | href()); ?> 26 |

27 | 28 | 29 |

30 | description()); ?> 31 |

32 | 33 | dependencies())): ?> 34 |

Dependencies

35 | 36 | 41 | 42 | 43 |

Details

44 | 45 |
46 |
Created
47 |
created()); ?>
48 |
Owner
49 |
owner()); ?>
50 |
Repository
51 |
repository()); ?>
52 |
Required php version
53 |
phpVersion()); ?>
54 |
License
55 |
licenseTitle()); ?> licenseHref() ? html_link($project->licenseHref()) : e($project->licenseHref()); ?>
56 |
57 | 58 |

Maintainers

59 | 60 | projectMaintainers() as $m): ?> 61 |
62 |
user
63 |
maintainer()->user()) ?>
64 |
role
65 |
type()) ?>
66 | maintainer()->name()) : ?> 67 |
name
68 |
maintainer()->name()) ?>
69 | 70 | maintainer()->email()) : ?> 71 |
email
72 |
maintainer()->email()) ?>
73 | 74 |
75 | 76 | 77 | -------------------------------------------------------------------------------- /script/test_all: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | read())) { 12 | $fullname = $search[$ii] . DIRECTORY_SEPARATOR . $entry; 13 | if (is_file($fullname) && preg_match($pattern, $entry)) { 14 | $files[] = $fullname; 15 | } elseif ($entry != '.' && $entry != '..' && is_dir($fullname)) { 16 | $search[] = $fullname; 17 | } 18 | } 19 | $dir->close(); 20 | } 21 | return $files; 22 | } 23 | $is_verbose = false; 24 | foreach ($argv as $arg) { 25 | $is_verbose = $is_verbose || $arg == '--verbose' || $arg == '-v'; 26 | } 27 | function verbose($str) { 28 | global $is_verbose; 29 | if ($is_verbose) { 30 | echo $str; 31 | } 32 | } 33 | $passes = 0; 34 | $failures = 0; 35 | $runs = 0; 36 | foreach (find_files(realpath(dirname(__FILE__) . '/..'), '/test\.php$/') as $filename) { 37 | chdir(dirname($filename)); 38 | $path = realpath(dirname(__FILE__) . '/../lib') . PATH_SEPARATOR . ini_get("include_path"); 39 | $xml = shell_exec('php -d include_path=' . escapeshellarg($path) . ' ' . escapeshellarg(basename($filename)) . ' -x'); 40 | verbose("-------------------------------------------\n"); 41 | verbose("Running suite: " . $filename . "\n"); 42 | $doc = new DomDocument(); 43 | if (@$doc->loadXml($xml)) { 44 | $q = new DomXpath($doc); 45 | $passes += $q->query('//pass')->length; 46 | $failures += $q->query('//fail')->length; 47 | foreach ($q->query('//fail') as $fail) { 48 | verbose($fail->nodeValue . "\n"); 49 | } 50 | verbose($q->query('//pass')->length . " passes, "); 51 | verbose($q->query('//fail')->length . " failues" . "\n"); 52 | } else { 53 | $failures += 1; 54 | verbose($xml); 55 | } 56 | $runs++; 57 | } 58 | verbose("===========================================\n"); 59 | if ($failures == 0) { 60 | echo "Done .. OK\n"; 61 | } else { 62 | echo "Done .. $runs tests completed with $passes passes and $failures failues.\n"; 63 | exit(1); 64 | } 65 | -------------------------------------------------------------------------------- /lib/components/login.php: -------------------------------------------------------------------------------- 1 | templates = $templates; 12 | $this->zend_auth = $zend_auth; 13 | $this->auth_cookies = $auth_cookies; 14 | $this->errors = array(); 15 | $this->user_factory = $user_factory; 16 | } 17 | function execute() { 18 | $this->url_state->init("continue", $this->url('/')); 19 | return parent::execute(); 20 | } 21 | function GET() { 22 | if ($this->query('openid_mode')) { 23 | $result = $this->authenticate(); 24 | if ($result instanceOf k_Response) { 25 | return $result; 26 | } 27 | } 28 | return parent::GET(); 29 | } 30 | function postForm() { 31 | $result = $this->authenticate(); 32 | if ($result instanceOf k_Response) { 33 | return $result; 34 | } 35 | return $this->render(); 36 | } 37 | function renderHtml() { 38 | $this->document->setTitle('Authentication required'); 39 | $t = $this->templates->create("login"); 40 | $response = new k_HtmlResponse( 41 | $t->render( 42 | $this, 43 | array( 44 | 'errors' => $this->errors))); 45 | $response->setStatus(401); 46 | return $response; 47 | } 48 | protected function authenticate() { 49 | $open_id_adapter = new Zend_Auth_Adapter_OpenId($this->body('openid_identifier')); 50 | $open_id_adapter->setReturnTo($this->url('', array('remember' => $this->body('remember')))); 51 | $open_id_adapter->setResponse(new ZfControllerResponseAdapter()); 52 | try { 53 | $result = $this->zend_auth->authenticate($open_id_adapter); 54 | } catch (ZfThrowableResponse $response) { 55 | return new k_SeeOther($response->getRedirect()); 56 | } 57 | $this->errors = array(); 58 | if ($result->isValid()) { 59 | $user = $this->selectUser($this->zend_auth->getIdentity()); 60 | if ($user) { 61 | $this->session()->set('identity', $user); 62 | if ($this->query('remember')) { 63 | $auth = new AuthenticationCookie( 64 | array( 65 | 'openid_identifier' => $user->user(), 66 | 'user_agent' => $this->header('User-Agent'), 67 | 'created' => date('Y-m-d H:i:s'))); 68 | $this->auth_cookies->delete(array('openid_identifier' => $user->user())); 69 | $this->auth_cookies->insert($auth); 70 | $this->cookie()->set('user', $auth->hash(), $auth->expire()); 71 | } 72 | return new k_SeeOther($this->query('continue')); 73 | } 74 | $this->errors[] = "Auth OK, but no such user on this system."; 75 | } 76 | $this->session()->set('identity', null); 77 | if ($this->cookie('user')) { 78 | $this->auth_cookies->delete(array('hash' => $this->cookie('user'))); 79 | $this->cookie()->set('user', null); 80 | } 81 | $this->zend_auth->clearIdentity(); 82 | foreach ($result->getMessages() as $message) { 83 | $this->errors[] = $message; 84 | } 85 | } 86 | protected function selectUser($openid_identity) { 87 | return $this->user_factory->create($openid_identity); 88 | } 89 | } 90 | -------------------------------------------------------------------------------- /templates/document.tpl.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | <?php e($title); ?> | pearhub.org 5 | 6 | pearhub.org 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | Fork me on GitHub 19 | 20 | 52 | 53 |
54 | 55 |

56 | 57 | 58 |
59 | 60 | 61 | 62 | 65 | 66 | 81 | 82 | 83 | 89 | 93 | 98 | 99 | -------------------------------------------------------------------------------- /lib/shell.inc.php: -------------------------------------------------------------------------------- 1 | temp_dir = sys_get_temp_dir(); 7 | } 8 | function runVarArgs($args) { 9 | $args[0] .= ' 2>&1'; 10 | $process = call_user_func_array(array($this, 'open'), $args); 11 | if ($this->debug) { 12 | echo "[Shell] ", implode(" ", $args), "\n"; 13 | } 14 | $result = $process->exec(); 15 | if ($result['result'] !== 0) { 16 | throw new ProcessExitException($result); 17 | } 18 | if ($this->debug && $result['stdout']) { 19 | echo $result['stdout'], "\n---\n"; 20 | } 21 | return $result['stdout']; 22 | } 23 | function run($command /*[, ...]*/) { 24 | $args = func_get_args(); 25 | return $this->runVarArgs($args); 26 | } 27 | function open($command /*[, ...]*/) { 28 | $args = func_get_args(); 29 | array_shift($args); 30 | $tokens = array(); 31 | foreach (preg_split('/(%s)/', $command, -1 , PREG_SPLIT_DELIM_CAPTURE) as $token) { 32 | if ($token === '%s') { 33 | if (count($args) === 0) { 34 | throw new Exception("Argument number mismatch"); 35 | } 36 | $tokens[] = escapeshellarg(array_shift($args)); 37 | } else { 38 | $tokens[] = $token; 39 | } 40 | } 41 | if (count($args) !== 0) { 42 | throw new Exception("Argument number mismatch"); 43 | } 44 | return new ShellProcess(implode($tokens)); 45 | } 46 | function getTempname() { 47 | $temp = tempnam($this->temp_dir, 'php'); 48 | if (file_exists($temp)) { 49 | unlink($temp); 50 | return $temp; 51 | } 52 | throw new Exception("Unable to reserve a temporary name"); 53 | } 54 | } 55 | 56 | class ProcessExitException extends Exception { 57 | protected $result; 58 | function __construct($result) { 59 | parent::__construct("Child process exited with error (".$result['result'].")"); 60 | $this->result = $result; 61 | } 62 | function stderr() { 63 | return $this->result['stderr']; 64 | } 65 | function stdout() { 66 | return $this->result['stdout']; 67 | } 68 | function __toString() { 69 | return parent::__toString() . "\nProcess stdout:\n" . $this->result['stdout'] . "\nProcess stderr:\n" . $this->result['stderr']; 70 | } 71 | } 72 | 73 | class ShellProcess { 74 | protected $command; 75 | function __construct($command) { 76 | $this->command = $command; 77 | } 78 | function exec($in = null, $cwd = null, $env = null) { 79 | $descriptorspec = array( 80 | 0 => array("pipe", "r"), 81 | 1 => array("pipe", "w"), 82 | 2 => array("pipe", "w") 83 | ); 84 | $cwd = $cwd ? $cwd : getcwd(); 85 | $env = $env ? $env : $_ENV; 86 | $process = proc_open($this->command, $descriptorspec, $pipes, $cwd, $env); 87 | if (is_resource($process)) { 88 | if ($in) { 89 | fwrite($pipes[0], $in); 90 | } 91 | fclose($pipes[0]); 92 | $out = stream_get_contents($pipes[1]); 93 | fclose($pipes[1]); 94 | $err = stream_get_contents($pipes[2]); 95 | fclose($pipes[2]); 96 | $return_value = proc_close($process); 97 | return array( 98 | 'stdout' => $out, 99 | 'stderr' => $err, 100 | 'result' => $return_value, 101 | ); 102 | } else { 103 | throw new Exception("Can't open sub-process"); 104 | } 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /lib/components/releases.php: -------------------------------------------------------------------------------- 1 | templates = $templates; 11 | $this->releases = $releases; 12 | $this->db = $db; 13 | } 14 | function renderHtml() { 15 | $project = $this->context->getProject(); 16 | $selection = iterator_to_array($this->releases->selectByProject($project)); 17 | $this->document->setTitle('Releases for ' . $project->displayName()); 18 | $this->document->addCrumb('releases', $this->url()); 19 | $t = $this->templates->create("releases/list"); 20 | return $t->render( 21 | $this, 22 | array( 23 | 'project' => $project, 24 | 'releases' => $selection)); 25 | } 26 | function renderHtmlCreate() { 27 | if ($this->identity()->anonymous()) { 28 | throw new k_NotAuthorized(); 29 | } 30 | $project = $this->context->getProject(); 31 | if (!$this->canCreate()) { 32 | throw new k_Forbidden(); 33 | } 34 | if ($this->body('version')) { 35 | $version = $this->body('version'); 36 | } else { 37 | $last_release = $this->releases->lastReleaseFor($project); 38 | if ($last_release) { 39 | list($major, $minor, $patch) = explode('.', $last_release->version()); 40 | $version = $major . '.' . $minor . '.' . ($patch + 1); 41 | } else { 42 | $version = '0.0.1'; 43 | } 44 | } 45 | $this->document->setTitle("Create release for " . $project->displayName()); 46 | $this->document->addCrumb('releases', $this->url()); 47 | $this->document->addCrumb("create", $this->url()); 48 | $t = $this->templates->create("releases/create"); 49 | return $t->render( 50 | $this, 51 | array( 52 | 'project' => $project, 53 | 'version' => $version, 54 | 'error' => $this->error)); 55 | } 56 | function postForm() { 57 | if (!$this->canCreate()) { 58 | throw new k_Forbidden(); 59 | } 60 | if ($this->processCreate()) { 61 | return new k_SeeOther($this->url()); 62 | } 63 | return $this->renderHtmlCreate(); 64 | } 65 | function processCreate() { 66 | if (!preg_match('/^\d+\.\d+\.\d+$/', $this->body('version'))) { 67 | $this->error = "Format of version must be X.X.X"; 68 | return false; 69 | } 70 | $project = $this->context->getProject(); 71 | $release = new Release( 72 | array( 73 | 'project_id' => $project->id(), 74 | 'version' => $this->body('version'), 75 | 'status' => 'building', 76 | 'mode' => 'manual')); 77 | $this->db->beginTransaction(); 78 | try { 79 | $this->releases->delete( 80 | array('project_id' => $project->id(), 'version' => $this->body('version'))); 81 | $this->releases->insert($release); 82 | $this->db->commit(); 83 | } catch (Exception $ex) { 84 | $this->db->rollback(); 85 | $this->error = $ex->getMessage(); 86 | return false; 87 | } 88 | return true; 89 | } 90 | function canCreate() { 91 | if ($this->identity()->anonymous()) { 92 | return false; 93 | } 94 | if (!$this->identity()->isAuthorized()) { 95 | return false; 96 | } 97 | $project = $this->context->getProject(); 98 | if ($project->owner() != $this->identity()->user()) { 99 | return false; 100 | } 101 | $last_release = $this->releases->lastReleaseFor($project); 102 | if ($last_release && in_array($last_release->status(), array('building', 'failed'))) { 103 | return false; 104 | } 105 | return true; 106 | } 107 | } -------------------------------------------------------------------------------- /lib/openid.inc.php: -------------------------------------------------------------------------------- 1 | _headersRaw; 16 | foreach ($this->_headers as $header) { 17 | $headers[] = $header['name'] . ': ' . $header['value']; 18 | } 19 | throw new ZfThrowableResponse( 20 | $this->_httpResponseCode, 21 | implode("", $this->_body), 22 | $headers); 23 | } 24 | } 25 | 26 | class ZfThrowableResponse extends Exception { 27 | function __construct($status, $body, $headers) { 28 | $this->status = $status; 29 | $this->body = $body; 30 | $this->headers = $headers; 31 | } 32 | function status() { 33 | return $this->status; 34 | } 35 | function body() { 36 | return $this->body; 37 | } 38 | function headers() { 39 | return $this->headers; 40 | } 41 | function getRedirect() { 42 | foreach ($this->headers as $header) { 43 | if (preg_match('/^location: (.*)$/i', $header, $reg)) { 44 | return $reg[1]; 45 | } 46 | } 47 | } 48 | } 49 | 50 | class SessionIdentityLoader implements k_IdentityLoader { 51 | function load(k_Context $context) { 52 | if ($context->session('identity')) { 53 | return $context->session('identity'); 54 | } 55 | return new k_Anonymous(); 56 | } 57 | } 58 | 59 | class NotAuthorizedComponent extends k_Component { 60 | function dispatch() { 61 | // redirect to login-page 62 | return new k_TemporaryRedirect($this->url('/login', array('continue' => $this->requestUri()))); 63 | } 64 | } 65 | 66 | class CookieIdentityLoader implements k_IdentityLoader { 67 | protected $gateway; 68 | protected $user_factory; 69 | function __construct(AuthenticationCookiesGateway $gateway, UserFactory $user_factory) { 70 | $this->gateway = $gateway; 71 | $this->user_factory = $user_factory; 72 | } 73 | function load(k_Context $context) { 74 | if ($context->session('identity')) { 75 | return $context->session('identity'); 76 | } 77 | if ($context->cookie('user')) { 78 | $auth = $this->gateway->fetchByHash($context->cookie('user')); 79 | if ($auth && $auth->validateContext($context)) { 80 | $user = $this->user_factory->create($auth->openidIdentifier()); 81 | $context->session()->set('identity', $user); 82 | return $user; 83 | } 84 | } 85 | return new k_Anonymous(); 86 | } 87 | } 88 | 89 | class UserFactory { 90 | public function create($username) { 91 | return new AuthenticatedUser($username); 92 | } 93 | } 94 | 95 | class AuthenticatedUser extends k_AuthenticatedUser { 96 | function isAuthorized() { 97 | return true; 98 | } 99 | } 100 | 101 | class AuthenticationCookiesGateway extends pdoext_TableGateway { 102 | function __construct(pdoext_Connection $db) { 103 | parent::__construct('authentication_cookies', $db); 104 | } 105 | function load($row = array()) { 106 | return new AuthenticationCookie($row); 107 | } 108 | function fetchByHash($hash) { 109 | return $this->fetch(array('hash' => $hash)); 110 | } 111 | } 112 | 113 | /* 114 | openid_identifier 115 | hash 116 | user_agent 117 | created 118 | */ 119 | class AuthenticationCookie { 120 | const SALT = "35d4gb6d4g63d4v3d4vd64fvd34v34xd34v4r54d3f4vv4df35v4d34vxd648fv4d"; 121 | protected $row; 122 | function __construct($row = array()) { 123 | $this->row = $row; 124 | } 125 | function validateContext(k_Context $context) { 126 | if ($this->expire() < time()) { 127 | return false; 128 | } 129 | return $context->header('User-Agent') == $this->userAgent(); 130 | } 131 | function getArrayCopy() { 132 | $this->generateHash(); 133 | return $this->row; 134 | } 135 | function openidIdentifier() { 136 | return $this->row['openid_identifier']; 137 | } 138 | function userAgent() { 139 | return $this->row['user_agent']; 140 | } 141 | function created() { 142 | return $this->row['created']; 143 | } 144 | function expire() { 145 | $t = strtotime($this->created()); 146 | return $t + 2592000; // 30 days 147 | } 148 | function hash() { 149 | return $this->generateHash(); 150 | } 151 | protected function generateHash() { 152 | $this->row['hash'] = md5(self::SALT . $this->openidIdentifier()); 153 | return $this->row['hash']; 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /lib/components/projects/list.php: -------------------------------------------------------------------------------- 1 | templates = $templates; 14 | $this->projects = $projects; 15 | $this->maintainers = $maintainers; 16 | $this->db = $db; 17 | $this->repo_probe = $repo_probe; 18 | } 19 | function map($name) { 20 | return 'components_projects_Entry'; 21 | } 22 | function renderHtml() { 23 | $this->document->setTitle("Projects"); 24 | $this->document->addCrumb('projects', $this->url()); 25 | $t = $this->templates->create('projects/list'); 26 | $selection = $this->projects->selectPaginated($this->query('page')); 27 | if ($this->query('q')) { 28 | $selection->setConjunctionOr(); 29 | $selection->addCriterion('name', '%' . $this->query('q') . '%', 'like'); 30 | $selection->addCriterion('summary', '%' . $this->query('q') . '%', 'like'); 31 | $selection->addCriterion('description', '%' . $this->query('q') . '%', 'like'); 32 | } elseif ($this->query('author')) { 33 | $selection->addCriterion('owner', urldecode($this->query('author')), '='); 34 | } 35 | return $t->render( 36 | $this, 37 | array( 38 | 'projects' => $selection)); 39 | } 40 | function forward($class_name, $namespace = "") { 41 | $this->document->addCrumb('projects', $this->url()); 42 | return parent::forward($class_name, $namespace); 43 | } 44 | function renderHtmlNew() { 45 | if ($this->identity()->anonymous()) { 46 | throw new k_NotAuthorized(); 47 | } 48 | if (!$this->canCreate()) { 49 | throw new k_Forbidden(); 50 | } 51 | if (!$this->project) { 52 | $this->project = new Project(); 53 | } 54 | $this->document->addScript($this->url('/res/form.js')); 55 | $this->document->setTitle("New project"); 56 | $this->document->addCrumb('projects', $this->url()); 57 | $this->document->addCrumb('new', $this->url('', array('new'))); 58 | $t = $this->templates->create('projects/new'); 59 | return $t->render($this, array('project' => $this->project)); 60 | } 61 | function renderJson() { 62 | return null; 63 | } 64 | function renderJsonMaintainers() { 65 | if ($this->identity()->anonymous()) { 66 | throw new k_NotAuthorized(); 67 | } 68 | $q = pdoext_query('maintainers'); 69 | $q->addColumn('user'); 70 | $q->addColumn('name'); 71 | $q->addColumn('email'); 72 | $q->addColumn('owner'); 73 | $q->addCriterion('user', $this->query('q') . '%', 'like'); 74 | $q->setLimit(10); 75 | $result = array(); 76 | foreach ($this->db->query($q) as $row) { 77 | $row['is_locked'] = $row['owner'] !== $this->identity()->user(); 78 | $result[] = $row; 79 | } 80 | return $result; 81 | } 82 | function postForm() { 83 | if ($this->processNew()) { 84 | return new k_SeeOther($this->url($this->project->name())); 85 | } 86 | return $this->render(); 87 | } 88 | function processNew() { 89 | if ($this->identity()->anonymous()) { 90 | throw new k_NotAuthorized(); 91 | } 92 | if (!$this->canCreate()) { 93 | throw new k_Forbidden(); 94 | } 95 | $this->project = new Project(); 96 | $this->project->unmarshal($this->body()); 97 | $this->project->unmarshalMaintainers($this->body(), $this->identity()->user(), $this->maintainers); 98 | /* 99 | try { 100 | $this->repo_probe->getRepositoryTypeAndCache($this->project->repositoryLocation(), true); 101 | } catch (Exception $ex) { 102 | $this->project->errors['repository'] = "Unable to detect repository. Please check that the URL is valid."; 103 | return false; 104 | } 105 | */ 106 | $this->db->beginTransaction(); 107 | try { 108 | $this->project->setOwner($this->identity()->user()); 109 | if (!$this->projects->insert($this->project)) { 110 | $this->db->rollback(); 111 | return false; 112 | } 113 | foreach ($this->project->projectMaintainers() as $m) { 114 | $this->maintainers->delete(array('user' => $m->maintainer()->user())); 115 | $this->maintainers->insert($m->maintainer()); 116 | } 117 | $this->db->commit(); 118 | } catch (Exception $ex) { 119 | $this->db->rollback(); 120 | $this->project->errors[] = $ex->getMessage(); 121 | return false; 122 | } 123 | if (!empty($GLOBALS['EMAIL_NOTIFY'])) { 124 | mail( 125 | $GLOBALS['EMAIL_NOTIFY'], 126 | "[pearhub] New project registered", 127 | var_export($this->body(), true)); 128 | } 129 | return true; 130 | } 131 | function canCreate() { 132 | if (!$this->identity()->anonymous() && $this->identity()->isAuthorized() === false) { 133 | return false; 134 | } 135 | return true; 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /templates/faq.tpl.php: -------------------------------------------------------------------------------- 1 |

2 | What is PEAR? 3 |

4 |

5 | PEAR is two things; It's a public repository of libraries, and it's a distribution system for publishing php libraries. Pearhub has nothing to do with the PEAR repository - it just uses the protocols of the distribution system. You can read more about PEAR on the pear website. 6 |

7 | 8 |

9 | Is there a tutorial somewhere? 10 |

11 |

12 | Glad you asked; Rob Morgan has put together a nice little tutorial, showing how to use pearhub with a project hosted on github. Give it a try. 13 |

14 | 15 |

16 | How do automatic releases work? 17 |

18 |

19 | Automatic releases are the preferred mode of publishing packages. When this is selected, pearhub will routinely check the repository for new tags, following the naming convention of X.X.X. If a new tag is found, a package will be generated. This means that all you have to do to roll a new release is to tag it in your repository. Nothing more. (Note that it may take a few minutes for the system to recognise it) 20 |

21 | 22 |

23 | How do manual releases work? 24 |

25 |

26 | While I recommend automatic releases, it isn't always possible to change the naming convension. In these cases, you can use manual releases. A manual release is initiated by going to a projects releases page and select the link "Create a new release". Enter the new version number and press the button. Then wait for the crontab to come by. This can take up a few minutes. 27 |

28 | 29 |

30 | What's this naming convension? 31 |

32 |

33 | For pearhub to pick up a tagged release, you need to follow a naming convension called Semantic Versioning. If you use subversion, you should tag your releases in tags/vX.X.X, where the X's are replaced with numbers. If you use git, tag your releases as vX.X.X. You may leave out the leading v. 34 |

35 | 36 |

37 | How do I tag a release in subversion? 38 |

39 |

40 | From the console, assuming that you stand in the repository root, type: 41 |

42 |
$ svn cp trunk tags/X.X.X
 43 | $ svn commit -m "Tagged release X.X.X" tags/X.X.X
44 | 45 |

46 | How do I tag a release in git? 47 |

48 |

49 | From the console, type: 50 |

51 |
$ git tag -a vX.X.X -m "Tagged release X.X.X"
 52 | $ git push origin : vX.X.X
53 | 54 |

55 | What's the meaning of those diferent numbers? 56 |

57 |

58 | The meaning of these are major, minor and patch. The first publicly available release should be 1.0.0. When you add new features, bump the minor number a notch. If you make major changes, breaking backwards compatibility, bump the major number. For smaller bug-fixes and additions, use the patch number. Note that numbers are not restricted to be a single digit - it's perfectly valid to have version numbers like 1.23.0. 59 |
60 | See: Semantic Versioning for a more detailed explanation. 61 |

62 | 63 |

64 | How do I create a profile? 65 |

66 |

67 | You don't - there's no need. Just login with an OpenID service, and we'll use that for controlling access rights. 68 |

69 | 70 |

71 | Can I manage someone else's project? 72 |

73 |

74 | Yes. If you need a pear package for a project, you should try to contact the maintainer first and have them setup the project here. If that's not possible, you can set it up your self. Unless they follow the required tagging convention, you'll have to run manual releases. 75 |

76 | 77 |

78 | Someone stole my project. The bastard! 79 |

80 |

81 | Chill. It's probably a fan who just wanted an easy way to install your awesome project. If you want to take over, please contact me and we'll sort the transfer out. 82 |

83 | 84 |

85 | Is pearhub affiliated with github? 86 |

87 |

88 | Nope. I'm just really lame at making up names. 89 |

90 | 91 |

92 | I have a not-so-frequent question. Where should I direct it? 93 |

94 |

95 | You really should use the feedback forum at uservoice, but if you can also contact me directly at mailing me. 96 |

97 | 98 |

99 | Who's behind this? 100 |

101 |

102 | Me. I made it in my spare time, because noone else did. I'm Troels Knak-Nielsen. The channel publishing is driven by pirum. The frontend is driven by Konstrukt. The OpenID integration comes from Zend Framework. 103 |

104 | 105 |

106 | Is the source code for the site available? 107 |

108 |

109 | Yes, you can grab it from github.com/offers/pearhub. 110 |

111 | 112 |

113 | Is pearhub one of a kind or are there any similar projects? 114 |

115 |

116 | I'm currently aware of pearfarm and openpear. They both offer a slightly different experience. 117 |

118 | -------------------------------------------------------------------------------- /lib/components/projects/entry.php: -------------------------------------------------------------------------------- 1 | templates = $templates; 14 | $this->projects = $projects; 15 | $this->maintainers = $maintainers; 16 | $this->db = $db; 17 | $this->repo_probe = $repo_probe; 18 | } 19 | function map($name) { 20 | if ($name === 'releases') { 21 | return 'components_Releases'; 22 | } 23 | return parent::map($name); 24 | } 25 | function forward($class_name, $namespace = "") { 26 | $this->document->addCrumb($this->project->name(), $this->url()); 27 | return parent::forward($class_name, $namespace); 28 | } 29 | function dispatch() { 30 | $this->project = $this->projects->fetch(array('name' => $this->name())); 31 | if (!$this->project) { 32 | throw new k_PageNotFound(); 33 | } 34 | return parent::dispatch(); 35 | } 36 | function renderHtml() { 37 | $this->document->setTitle($this->project->name()); 38 | $this->document->addCrumb($this->project->name(), $this->url()); 39 | $this->document->addScript($this->url('/res/form.js')); 40 | $t = $this->templates->create("projects/show"); 41 | return $t->render($this, array('project' => $this->project)); 42 | } 43 | function renderJson() { 44 | return $this->project->getArrayCopy(); 45 | } 46 | function renderXml() { 47 | $xml = new XmlWriter(); 48 | $xml->openMemory(); 49 | $xml->startElement('project'); 50 | foreach ($this->project->getArrayCopy() as $key => $value) { 51 | $xml->writeElement(str_replace('_', '-', $key), $value); 52 | } 53 | $xml->endElement(); 54 | return $xml->outputMemory(); 55 | } 56 | function renderHtmlEdit() { 57 | if ($this->identity()->anonymous()) { 58 | throw new k_NotAuthorized(); 59 | } 60 | if (!$this->canEdit()) { 61 | throw new k_Forbidden(); 62 | } 63 | $this->document->addScript($this->url('/res/form.js')); 64 | $this->document->setTitle("Edit " . $this->project->displayName()); 65 | $this->document->addCrumb($this->project->displayName(), $this->url()); 66 | $this->document->addCrumb("edit", $this->url('', array('edit'))); 67 | $t = $this->templates->create("projects/edit"); 68 | return $t->render($this, array('project' => $this->project)); 69 | } 70 | function putForm() { 71 | if ($this->processUpdate()) { 72 | return new k_SeeOther($this->url('../' . $this->project->name())); 73 | } 74 | return $this->render(); 75 | } 76 | function processUpdate() { 77 | if ($this->identity()->anonymous()) { 78 | throw new k_NotAuthorized(); 79 | } 80 | if (!$this->canEdit()) { 81 | throw new k_Forbidden(); 82 | } 83 | $body = $this->body(); 84 | 85 | foreach ($this->project->projectMaintainers() as $old_maintainer) { 86 | if ($old_maintainer->maintainer()->owner() != $this->identity()->user()) { 87 | $uniqid = uniqid(); 88 | $body['maintainers'][$uniqid]['user'] = $old_maintainer->maintainer()->user(); 89 | $body['maintainers'][$uniqid]['name'] = $old_maintainer->maintainer()->name(); 90 | $body['maintainers'][$uniqid]['email'] = $old_maintainer->maintainer()->email(); 91 | $body['maintainers'][$uniqid]['type'] = $old_maintainer->type(); 92 | } 93 | } 94 | $this->project->unmarshal($body); 95 | if (!$this->project->unmarshalMaintainers($body, $this->identity()->user(), $this->maintainers)) { 96 | return false; 97 | } 98 | /* 99 | try { 100 | $this->repo_probe->getRepositoryTypeAndCache($this->project->repositoryLocation(), true); 101 | } catch (Exception $ex) { 102 | $this->project->errors['repository'] = "Unable to detect repository. Please check that the URL is valid."; 103 | return false; 104 | } 105 | */ 106 | $this->db->beginTransaction(); 107 | try { 108 | if (!$this->projects->update($this->project)) { 109 | $this->db->rollback(); 110 | return false; 111 | } 112 | foreach ($this->project->projectMaintainers() as $m) { 113 | $this->maintainers->delete(array('user' => $m->maintainer()->user())); 114 | $this->maintainers->insert($m->maintainer()); 115 | } 116 | $this->db->commit(); 117 | } catch (Exception $ex) { 118 | $this->db->rollback(); 119 | $this->debug($ex); 120 | $this->project->errors[] = $ex->getMessage(); 121 | return false; 122 | } 123 | return true; 124 | } 125 | function renderHtmlDelete() { 126 | if ($this->identity()->anonymous()) { 127 | throw new k_NotAuthorized(); 128 | } 129 | if (!$this->canDelete()) { 130 | throw new k_Forbidden(); 131 | } 132 | $this->document->setTitle("Delete " . $this->project->displayName()); 133 | $this->document->addCrumb($this->project->displayName(), $this->url()); 134 | $this->document->addCrumb("delete", $this->url('', array('delete'))); 135 | $t = $this->templates->create("projects/delete"); 136 | return $t->render($this, array('project' => $this->project)); 137 | } 138 | function DELETE() { 139 | if ($this->identity()->anonymous()) { 140 | throw new k_NotAuthorized(); 141 | } 142 | if (!$this->canDelete()) { 143 | throw new k_Forbidden(); 144 | } 145 | if ($this->projects->delete(array('id' => $this->project->id()))) { 146 | return new k_SeeOther($this->url('..')); 147 | } 148 | return $this->render(); 149 | } 150 | function getProject() { 151 | return $this->project; 152 | } 153 | function canEdit() { 154 | if ($this->identity()->anonymous()) { 155 | return false; 156 | } 157 | if (!$this->identity()->isAuthorized()) { 158 | return false; 159 | } 160 | return $this->project->owner() == $this->identity()->user(); 161 | } 162 | function canDelete() { 163 | if ($this->identity()->anonymous()) { 164 | return false; 165 | } 166 | if (!$this->identity()->isAuthorized()) { 167 | return false; 168 | } 169 | if ($this->project->owner() != $this->identity()->user()) { 170 | return false; 171 | } 172 | // Allow deleting a project for up to 24h 173 | return strtotime($this->project->created()) + 86400 > time(); 174 | } 175 | } 176 | -------------------------------------------------------------------------------- /templates/projects/form.tpl.php: -------------------------------------------------------------------------------- 1 |
2 | 3 | summary(), array('id' => "field-summary")); ?> 4 | 5 |
6 | 7 |
8 | 9 | description(), array('id' => "field-description")); ?> 10 | 11 |
12 | 13 |
14 | 15 | repository(), array('id' => "field-repository")); ?> 16 | 17 |
18 | 19 |
20 |
21 | 24 | 25 | 28 | 29 |
30 |
31 | 32 |

files

33 | 34 |
35 |
36 | 39 | 40 | 43 | 44 | 47 | 48 |
49 |
50 | 51 |

maintainers

52 | 53 |
54 |
55 | projectMaintainers() as $m): ?> 56 | maintainer()->owner() !== $context->identity()->user(); ?> 57 | 58 |
59 | 60 | 61 | 62 | 63 | 64 | 69 |
70 | 71 |
72 |
73 | Add maintainer 74 |
75 | 76 |
77 | 78 |

dependencies

79 | 80 |
81 |
82 | dependencies() as $dep): ?> 83 |
84 | 85 | 86 | 87 | 88 |
89 | 90 |
91 |
92 | Add Dependency 93 |
94 | 95 |
96 | 97 |

details

98 | 99 |
100 | 101 | 102 |
103 | 104 |
105 | 106 | 107 |
108 | 109 |

license

110 | 111 |
112 |
113 | 114 | 115 | 116 | 117 | 118 |
119 |
120 | 121 |

release policy

122 | 123 |
124 |
125 | releasePolicy() == "auto", array('id' => "field-release-policy-auto", 'label' => "Automatic")); ?> 126 |

127 | Releases are automatically created whenever a new tag is created in the repository. This is the default and recommended way to manage releases. 128 |

129 | 130 | releasePolicy() == "manual", array('id' => "field-release-policy-manual", 'label' => "Manual")); ?> 131 |

132 | Releases are made by explicitly requesting a new release from this site. You can use this if you don't want to follow the standard repository layout or if you don't have control over the repository () 133 |

134 | 135 |
136 |
137 | 138 |
139 |
140 |
141 |
142 | 143 | 144 | 145 | 146 | -------------------------------------------------------------------------------- /script/tasks: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | get('ReleaseGateway'); 12 | $release = new Release( 13 | array( 14 | 'project_id' => $project->id(), 15 | 'version' => $version, 16 | 'status' => 'building', 17 | 'mode' => 'auto')); 18 | $gateway_releases->insert($release); 19 | } 20 | 21 | function build_release($release, $project) { 22 | global $container; 23 | $repo_probe = $container->get('RepoProbe'); 24 | $gateway_releases = $container->get('ReleaseGateway'); 25 | $shell = $container->get('Shell'); 26 | $local_copy = null; 27 | 28 | try { 29 | $repo = $repo_probe->getRepositoryAccess($project); 30 | if ($release->mode() == 'auto') { 31 | $local_copy = $repo->exportTag($release->version()); 32 | $stability = $repo->getTagInfo($release->version())->stability(); 33 | } else { 34 | $local_copy = $repo->exportRevision('HEAD'); 35 | $stability = 'stable'; 36 | } 37 | $files = new FileFinder($local_copy->getPath()); 38 | $files->traverse($project->path(), $project->ignore(), $project->destination()); 39 | $builder = $container->get('PackageBuilder'); 40 | $builder->build($local_copy, $files, $project, $release->version(), $stability); 41 | $release->setCompleted(); 42 | $gateway_releases->update( 43 | $release, 44 | array( 45 | 'project_id' => $project->id(), 46 | 'version' => $release->version())); 47 | } catch (Exception $ex) { 48 | $release->setFailed($ex->__toString()); 49 | $gateway_releases->update( 50 | $release, 51 | array( 52 | 'project_id' => $project->id(), 53 | 'version' => $release->version())); 54 | echo "Caught Exception at " . __FILE__ . " : " . __LINE__ . "\n"; 55 | echo $ex; 56 | } 57 | 58 | // Clean up working copy from temp folder 59 | if(null !== $local_copy) { 60 | $local_copy->destroy($shell); 61 | } 62 | } 63 | 64 | function task_rebuild_release($project_name, $release_version) { 65 | global $container; 66 | $gateway_project = $container->get('ProjectGateway'); 67 | $gateway_releases = $container->get('ReleaseGateway'); 68 | $project = $gateway_project->fetch(array('name' => $project_name)); 69 | if (!$project) { 70 | throw new Exception("Can't find project " . $project); 71 | } 72 | $release = $gateway_releases->fetch(array('project_id' => $project->id(), 'version' => $release_version)); 73 | if (!$release) { 74 | throw new Exception("Can't find release " . $release_version); 75 | } 76 | $release->setBuilding(); 77 | $gateway_releases->update($release, array('project_id' => $project->id(), 'version' => $release->version())); 78 | } 79 | 80 | function task_delete_release($project_name, $release_version) { 81 | global $container; 82 | $gateway_project = $container->get('ProjectGateway'); 83 | $gateway_releases = $container->get('ReleaseGateway'); 84 | $builder = $container->get('PackageBuilder'); 85 | $project = $gateway_project->fetch(array('name' => $project_name)); 86 | if (!$project) { 87 | throw new Exception("Can't find project " . $project); 88 | } 89 | $release = $gateway_releases->fetch(array('project_id' => $project->id(), 'version' => $release_version)); 90 | if (!$release) { 91 | throw new Exception("Can't find release " . $release_version); 92 | } 93 | $gateway_releases->delete(array('project_id' => $project->id(), 'version' => $release->version())); 94 | $builder->deletePackage($project, $release_version); 95 | task_pirum_build_channel(); 96 | } 97 | 98 | function task_update_latest_versions() { 99 | global $container; 100 | $gateway_project = $container->get('ProjectGateway'); 101 | $gateway_releases = $container->get('ReleaseGateway'); 102 | foreach ($gateway_project->select() as $project) { 103 | $gateway_project->updateRevisionInfo( 104 | $project->id(), 105 | $gateway_releases->lastReleaseFor($project->id())); 106 | } 107 | } 108 | 109 | function task_list_projects() { 110 | global $container; 111 | $gateway_project = $container->get('ProjectGateway'); 112 | foreach ($gateway_project->select() as $project) { 113 | echo " ", $project->name(), "\n"; 114 | } 115 | } 116 | 117 | function task_list_project_releases($project_name) { 118 | global $container; 119 | $gateway_project = $container->get('ProjectGateway'); 120 | $gateway_releases = $container->get('ReleaseGateway'); 121 | $project = $gateway_project->fetch(array('name' => $project_name)); 122 | if (!$project) { 123 | throw new Exception("Can't find project " . $project); 124 | } 125 | echo "Releases for ", $project_name, "\n"; 126 | foreach ($gateway_releases->selectByProject($project) as $release) { 127 | echo " ", $release->version(), "\n"; 128 | } 129 | } 130 | 131 | function task_sweep_projects() { 132 | global $container; 133 | $gateway_project = $container->get('ProjectGateway'); 134 | $gateway_releases = $container->get('ReleaseGateway'); 135 | $repo_probe = $container->get('RepoProbe'); 136 | foreach ($gateway_project->selectWithAutomaticReleasePolicy() as $project) { 137 | try { 138 | $repo = $repo_probe->getRepositoryAccess($project); 139 | $latestTag = $repo->latestTag(); 140 | if ($latestTag) { 141 | $lastRelease = $gateway_releases->lastReleaseFor($project); 142 | if (!$lastRelease || ($latestTag > $lastRelease->version())) { 143 | create_release($latestTag, $project); 144 | } 145 | } 146 | } catch (Exception $ex) { 147 | echo "Caught Exception at " . __FILE__ . " : " . __LINE__ . "\n"; 148 | echo $ex; 149 | } 150 | } 151 | } 152 | 153 | function get_pending_releases() { 154 | global $container; 155 | $gateway_project = $container->get('ProjectGateway'); 156 | $gateway_release = $container->get('ReleaseGateway'); 157 | $result = array(); 158 | foreach ($gateway_release->selectPendingBuild() as $release) { 159 | $project = $gateway_project->fetch(array('id' => $release->projectId())); 160 | if ($project) { 161 | $result[] = array('project' => $project, 'release' => $release); 162 | } 163 | } 164 | return $result; 165 | } 166 | 167 | function task_build_pending_releases() { 168 | $found = false; 169 | foreach (get_pending_releases() as $release) { 170 | build_release($release['release'], $release['project']); 171 | $found = true; 172 | } 173 | if ($found) { 174 | task_pirum_build_channel(); 175 | } 176 | } 177 | 178 | function task_list_pending_releases() { 179 | foreach (get_pending_releases() as $release) { 180 | echo $release['project']->name(), " (", $release['release']->version(), ")\n"; 181 | } 182 | } 183 | 184 | function task_list_tasks() { 185 | $defined_functions = get_defined_functions(); 186 | echo "Available tasks:\n"; 187 | foreach ($defined_functions['user'] as $fn) { 188 | if (preg_match('/^task_(.+)$/', $fn, $reg)) { 189 | echo " ", $reg[1], "\n"; 190 | } 191 | } 192 | } 193 | 194 | ob_start(); 195 | require_once 'thirdparty/pirum'; 196 | ob_clean(); 197 | class Pearhub_PirumBuilder extends Pirum_Builder { 198 | protected function buildIndex() {} 199 | protected function buildCss() {} 200 | protected function updateIndex() {} 201 | protected function updateCss() {} 202 | } 203 | 204 | function task_pirum_build_channel() { 205 | global $container; 206 | $builder = $container->get('Pearhub_PirumBuilder'); 207 | $builder->build(); 208 | 209 | /* clean up after pirum build in /tmp */ 210 | $shell = $container->get('Shell'); 211 | $temp_dir = sys_get_temp_dir() . "/pirum*"; 212 | $shell->run("rm -rf $temp_dir"); 213 | } 214 | 215 | if (realpath($_SERVER['PHP_SELF']) == __FILE__) { 216 | $container = create_container(); 217 | flock($file_lock = fopen(__FILE__, 'r'), LOCK_EX | LOCK_NB) or exit; 218 | if (isset($_SERVER['DEBUG']) && $_SERVER['DEBUG']) { 219 | $container->get('Shell')->debug = true; 220 | $container->get('RepoProbe')->debug = true; 221 | } 222 | try { 223 | if (isset($_SERVER['argv'][1]) && function_exists('task_'.$_SERVER['argv'][1])) { 224 | $args = $_SERVER['argv']; 225 | array_shift($args); 226 | array_shift($args); 227 | call_user_func_array('task_'.$_SERVER['argv'][1], $args); 228 | } else { 229 | task_list_tasks(); 230 | } 231 | } catch (Exception $ex) { 232 | if (isset($_SERVER['EMAIL_FAILURE'])) { 233 | mail($_SERVER['EMAIL_FAILURE'], implode(" ", $_SERVER['argv']) . " FAILED!", $ex->__toString()); 234 | } 235 | echo "Caught Exception at " . __FILE__ . " : " . __LINE__ . "\n"; 236 | echo $ex; 237 | exit -1; 238 | } 239 | } 240 | -------------------------------------------------------------------------------- /www/res/style.css: -------------------------------------------------------------------------------- 1 | /* * * * common * * * */ 2 | html { 3 | background: #111111 url(bg.jpg) repeat 0 0; 4 | } 5 | 6 | body { 7 | font-size: 100%; 8 | color: #333; 9 | text-align: center; 10 | } 11 | 12 | body, 13 | #profile #logout, 14 | #openid .submit { 15 | font-family: Bookman, Georgia; 16 | } 17 | 18 | a { 19 | color: #808; 20 | text-decoration: none; 21 | } 22 | a:hover { 23 | color: #c28; 24 | background-color: #808; 25 | } 26 | 27 | em, em strong, strong em { 28 | font-style: italic; 29 | } 30 | 31 | strong, em strong, strong em { 32 | font-weight: bold; 33 | } 34 | 35 | #header, #content { 36 | width: 40em; 37 | margin: 0 auto; 38 | background: #eee; 39 | padding: 0 2em; 40 | font-size: 1em; 41 | text-align: left; 42 | } 43 | 44 | #header { 45 | margin: 1em auto; 46 | border-top: .5em solid #eee; 47 | border-bottom: .5em solid #eee; 48 | } 49 | 50 | #content { 51 | margin-bottom: 1em; 52 | } 53 | 54 | /* 55 | #header strong { 56 | font-weight: bold; 57 | } 58 | */ 59 | 60 | #crumbtrail { 61 | font-size: 150%; 62 | font-weight: bold; 63 | } 64 | 65 | #search { 66 | padding: .25em; 67 | text-align: right; 68 | position: relative; 69 | bottom: 3em; 70 | float: right; 71 | } 72 | 73 | #q { 74 | font-family: Monaco, Deja Vu Sans Mono, Inconsolata, Consolas, monospace; 75 | } 76 | 77 | #profile { 78 | text-align: right; 79 | } 80 | 81 | #profile #logout, #profile a { 82 | padding: 0; 83 | margin: 0; 84 | border: 2px solid #333; 85 | font-size: 1em; 86 | background-color: #333; 87 | color: #eee; 88 | 89 | font-weight: bold; 90 | cursor: pointer; 91 | -moz-border-radius: 5px; 92 | -webkit-border-radius: 5px; 93 | } 94 | 95 | #profile #logout:hover, #profile a:hover { 96 | color: #c28; 97 | background-color: #808; 98 | border-color: #808; 99 | } 100 | 101 | #content { 102 | padding-top: 1em; 103 | padding-bottom: 2em; 104 | } 105 | 106 | #content h1 { 107 | font-weight: normal; 108 | font-size: 300%; 109 | color: #444; 110 | margin-bottom: .25em; 111 | } 112 | 113 | #content h2 { 114 | color: #444; 115 | font-size: 150%; 116 | border-bottom: 1px solid #888; 117 | margin: .25em 0; 118 | } 119 | 120 | #content h4 { 121 | color: #888; 122 | font-size: 300%; 123 | font-weight: bold; 124 | /*text-align: center;*/ 125 | } 126 | 127 | #content p.question { 128 | font-weight: bold; 129 | margin-bottom: 0; 130 | } 131 | 132 | #content p, #content dl, #content ul, #content label.radio-button { 133 | font-size: 125%; 134 | color: #888; 135 | } 136 | 137 | #content label.radio-button { 138 | font-weight: bold; 139 | } 140 | 141 | #content dt { 142 | font-weight: bold; 143 | } 144 | 145 | #content dd { 146 | margin-left: 1em; 147 | } 148 | 149 | #content p { 150 | margin-bottom: 1em; 151 | } 152 | 153 | #content pre { 154 | font-size: 1.5em; 155 | margin-bottom: 1em; 156 | padding: .5em; 157 | background-color: #444; 158 | color: #0f0; 159 | width: 100%; 160 | overflow-x: auto; 161 | } 162 | 163 | /* * * * list * * * */ 164 | .list { 165 | border-top: 1px solid #888; 166 | margin: 0 0 2em 0; 167 | clear: both; 168 | } 169 | .list li { 170 | border-bottom: 1px solid #888; 171 | font-size: 1em; 172 | } 173 | .list li a { 174 | display: block; 175 | padding: 0.5em; 176 | color: #888; 177 | font-size: 0.8em; 178 | font-weight: normal; 179 | } 180 | .list li a span { 181 | color: #808; 182 | font-size: 250%; 183 | font-weight: bold; 184 | } 185 | .list li a:hover span { 186 | color: #c28; 187 | background-color: #808; 188 | } 189 | .list li a em { 190 | color: #ccc; 191 | font-size: 250%; 192 | font-weight: bold; 193 | font-style: normal; 194 | margin-left: .5em; 195 | } 196 | /* * * * releases * * * */ 197 | #releases { 198 | border-top: 1px solid #888; 199 | margin: 1em 0 2em; 200 | } 201 | #releases li { 202 | border-bottom: 1px solid #888; 203 | } 204 | #releases h3 { 205 | font-size: 1.5em; 206 | } 207 | #releases p { 208 | font-size: 1em; 209 | margin-bottom: .5em; 210 | } 211 | 212 | /* * * * form * * * */ 213 | .form { 214 | padding: .25em; 215 | margin: .25em 0; 216 | width: auto; 217 | } 218 | .form label { 219 | color: #888; 220 | display: block; 221 | clear: left; 222 | } 223 | .form label span { 224 | display: block; 225 | width: 8em; 226 | line-height: 200%; 227 | float: left; 228 | } 229 | .form input, 230 | .form textarea, 231 | .form select { 232 | color: #444; 233 | font-size: 150%; 234 | font-family: Monaco, Deja Vu Sans Mono, Inconsolata, Consolas, monospace; 235 | } 236 | .form input[type=text], 237 | .form textarea { 238 | width: 12em; 239 | } 240 | .form textarea { 241 | height: 6em; 242 | } 243 | .full input[type=text], 244 | .full textarea, 245 | .full select { 246 | width: 100%; 247 | } 248 | .form-footer { 249 | border-top: 2px solid #888; 250 | font-size: 150%; 251 | padding: .25em; 252 | text-align: right; 253 | margin: 0; 254 | } 255 | /* * * * fieldsets * * * */ 256 | /* 257 | .form h2 { 258 | color: #888; 259 | font-size: 150%; 260 | border-bottom: 1px solid #888; 261 | } 262 | */ 263 | .container { 264 | margin-top: .25em; 265 | padding-top: .25em; 266 | } 267 | .fieldset { 268 | clear: both; 269 | margin-top: .5em; 270 | } 271 | .remove { 272 | float: right; 273 | font-size: 200%; 274 | color: #888; 275 | text-decoration: none; 276 | } 277 | .remove:hover { 278 | color: #800080; 279 | background-color: transparent; 280 | } 281 | .append-wrapper { 282 | text-align: center; 283 | } 284 | .append { 285 | display: inline-block; 286 | color: #800080; 287 | text-decoration: none; 288 | background-color: #eee; 289 | padding: .25em .5em; 290 | margin-top: .5em; 291 | margin-bottom: 1px; 292 | text-align: center; 293 | } 294 | .append:hover { 295 | background: url(overlay-button.png) repeat-x; 296 | background-color: #c28; 297 | margin-bottom: 0; 298 | color: #fff; 299 | -moz-border-radius: 5px; 300 | -webkit-border-radius: 5px; 301 | -moz-box-shadow: 0 1px 3px rgba(0,0,0,0.5); 302 | -webkit-box-shadow: 0 1px 3px rgba(0,0,0,0.5); 303 | text-shadow: 0 -1px 1px rgba(0,0,0,0.25); 304 | border-bottom: 1px solid rgba(0,0,0,0.25); 305 | } 306 | 307 | /* * * * maintainers-autocomplete * * * */ 308 | #maintainers-autocomplete { 309 | display: none; 310 | position: absolute; 311 | background-color: #eee; 312 | padding: .25em; 313 | } 314 | #maintainers-autocomplete ul { 315 | margin: 0; 316 | padding: 0; 317 | list-style: none; 318 | } 319 | #maintainers-autocomplete li { 320 | color: #888; 321 | margin: 0; 322 | padding: 0; 323 | cursor: pointer; 324 | } 325 | #maintainers-autocomplete li.selected { 326 | color: #000; 327 | background-color: #E0ECFF; 328 | } 329 | 330 | /* * * * tooltip * * * */ 331 | #tooltip { 332 | display: none; 333 | position: absolute; 334 | padding: .25em; 335 | color: #fff; 336 | font-size: 1em; 337 | width: 24em; 338 | /* background: rgba(0, 0, 0, 0.6); /* todo: this doesn't work in ie */ 339 | background: black; 340 | /* opacity */ 341 | opacity: .75; /* Standard: FF gt 1.5, Opera, Safari */ 342 | filter: alpha(opacity=75); /* IE lt 8 */ 343 | -ms-filter: "alpha(opacity=75)"; /* IE 8 */ 344 | -khtml-opacity: .75; /* Safari 1.x */ 345 | -moz-opacity: .75; /* FF lt 1.5, Netscape */ 346 | /* rounded corners */ 347 | -moz-border-radius: 5px; 348 | -webkit-border-radius: 5px; 349 | } 350 | #tooltip h3 { 351 | font-size: 1.5em; 352 | } 353 | #tooltip p { 354 | color: #fff; 355 | margin: 0; 356 | } 357 | 358 | 359 | /* * * * zurb awesome-button * * * */ 360 | 361 | .form-footer input, #search .submit, #openid .submit { 362 | background: url(overlay-button.png) repeat-x; 363 | display: inline-block; 364 | color: #fff; 365 | text-decoration: none; 366 | position: relative; 367 | cursor: pointer; 368 | border: 0; 369 | padding: .25em .5em; 370 | background-color: #808; 371 | -moz-border-radius: 5px; 372 | -webkit-border-radius: 5px; 373 | -moz-box-shadow: 0 1px 3px rgba(0,0,0,0.5); 374 | -webkit-box-shadow: 0 1px 3px rgba(0,0,0,0.5); 375 | text-shadow: 0 -1px 1px rgba(0,0,0,0.25); 376 | border-bottom: 1px solid rgba(0,0,0,0.25); 377 | } 378 | .form-footer input:hover, #search .submit:hover, #openid .submit:hover { 379 | background-color: #c28; 380 | color: #fff; 381 | } 382 | 383 | .form-footer input.warning { 384 | background-color: #800; 385 | } 386 | .form-footer input.warning:hover { 387 | background-color: #c00; 388 | } 389 | 390 | #openid input { 391 | color: #444; 392 | font-size: 150%; 393 | font-family: Monaco, Deja Vu Sans Mono, Inconsolata, Consolas, monospace; 394 | } 395 | #openid label { 396 | color: #888; 397 | } 398 | 399 | .pagination { 400 | margin-bottom: 1em; 401 | } 402 | .pagination a { 403 | padding: 0 .5em; 404 | } 405 | 406 | .pagination span { 407 | margin: 0 .5em; 408 | } 409 | 410 | #my-projects { 411 | margin-top: 10px; 412 | text-align: right; 413 | width: 100px; 414 | float: right; 415 | font-weight: bold; 416 | } 417 | 418 | .clear { 419 | clear: both; 420 | } -------------------------------------------------------------------------------- /lib/viewhelpers.inc.php: -------------------------------------------------------------------------------- 1 | document()->addOnload($js); 8 | } 9 | 10 | /** 11 | */ 12 | function add_stylesheet($url) { 13 | $GLOBALS['k_current_context']->document()->addStyle($url); 14 | } 15 | 16 | /** 17 | */ 18 | function add_javascript($url) { 19 | $GLOBALS['k_current_context']->document()->addScript($url); 20 | } 21 | 22 | /** 23 | * Returns querystring value. 24 | */ 25 | function query($key, $default = null) { 26 | return $GLOBALS['k_current_context']->query($key, $default); 27 | } 28 | 29 | function html_link($url, $title = null, $options = array()) { 30 | if ($title === null) { 31 | $title = $url; 32 | } 33 | $options['href'] = $url; 34 | $html = " $v) { 36 | if ($v !== null) { 37 | $html .= ' ' . escape($k) . '="' . escape($v) . '"'; 38 | } 39 | } 40 | $html .= ">".escape($title).""; 41 | return $html; 42 | } 43 | 44 | /** 45 | * Generates an opening html `
` tag. 46 | */ 47 | function html_form_tag($method = 'post', $action = null, $options = array()) { 48 | $method = strtolower($method); 49 | $html = 'url(); 51 | $options['method'] = $method === 'get' ? 'get' : 'post'; 52 | foreach ($options as $k => $v) { 53 | if ($v !== null) { 54 | $html .= ' ' . escape($k) . '="' . escape($v) . '"'; 55 | } 56 | } 57 | $html .= ">\n"; 58 | if ($method !== 'get' && $method !== 'post') { 59 | $html .= ' 60 | '; 61 | } 62 | return $html; 63 | } 64 | 65 | function html_text_field($name, $value = null, $options = array()) { 66 | $html = ' $v) { 70 | if ($v !== null) { 71 | $html .= ' ' . escape($k) . '="' . escape($v) . '"'; 72 | } 73 | } 74 | return $html . " />\n"; 75 | } 76 | 77 | function html_password_field($name, $options = array()) { 78 | $html = ' $v) { 82 | if ($v !== null) { 83 | $html .= ' ' . escape($k) . '="' . escape($v) . '"'; 84 | } 85 | } 86 | return $html . " />\n"; 87 | } 88 | 89 | function html_hidden_field($name, $value = null, $options = array()) { 90 | $html = ' $v) { 94 | if ($v !== null) { 95 | $html .= ' ' . escape($k) . '="' . escape($v) . '"'; 96 | } 97 | } 98 | return $html . " />\n"; 99 | } 100 | 101 | function html_text_area($name, $value = null, $options = array()) { 102 | $html = ' $v) { 105 | if ($v !== null) { 106 | $html .= ' ' . escape($k) . '="' . escape($v) . '"'; 107 | } 108 | } 109 | return $html . ">" . escape($value) . "\n"; 110 | } 111 | 112 | function html_radio($name, $value = null, $checked = false, $options = array()) { 113 | $html = ""; 114 | if (isset($options['label'])) { 115 | $label = $options['label']; 116 | $options['label'] = null; 117 | $html .= ''; 131 | } 132 | return $html . "\n"; 133 | } 134 | 135 | function html_checkbox($name, $checked = false, $options = array()) { 136 | $html = ""; 137 | if (isset($options['label'])) { 138 | $label = $options['label']; 139 | $options['label'] = null; 140 | $html .= ''; 154 | } 155 | return $html . "\n"; 156 | } 157 | 158 | function html_select($name, $values = array(), $value = null, $options = array()) { 159 | $html = ' $v) { 162 | if ($v !== null) { 163 | $html .= ' ' . escape($k) . '="' . escape($v) . '"'; 164 | } 165 | } 166 | return $html . ">" . html_options($values, $value) . "\n"; 167 | } 168 | 169 | /** 170 | * Renders html `