Jordi Boggiano 14 anni fa
commit
4c7b8e0226
47 ha cambiato i file con 2634 aggiunte e 0 eliminazioni
  1. 6 0
      .gitignore
  2. 19 0
      LICENSE
  3. 1 0
      app/.htaccess
  4. 9 0
      app/AppCache.php
  5. 53 0
      app/AppKernel.php
  6. 13 0
      app/Resources/views/base.html.twig
  7. 33 0
      app/autoload.php
  8. 84 0
      app/check.php
  9. 57 0
      app/config/config.yml
  10. 23 0
      app/config/config_dev.yml
  11. 18 0
      app/config/config_prod.yml
  12. 14 0
      app/config/config_test.yml
  13. 18 0
      app/config/parameters.ini
  14. 3 0
      app/config/routing.yml
  15. 14 0
      app/config/routing_dev.yml
  16. 41 0
      app/config/security.yml
  17. 16 0
      app/console
  18. BIN
      app/java/compiler.jar
  19. BIN
      app/java/yuicompressor-2.4.2.jar
  20. 41 0
      app/phpunit.xml.dist
  21. 1 0
      bin/.htaccess
  22. 99 0
      bin/build.sh
  23. 92 0
      bin/build_bootstrap
  24. 121 0
      bin/vendors
  25. 1 0
      src/.htaccess
  26. 62 0
      src/Packagist/WebBundle/Command/UpdatePackagesCommand.php
  27. 49 0
      src/Packagist/WebBundle/Controller/ApiController.php
  28. 125 0
      src/Packagist/WebBundle/Controller/WebController.php
  29. 201 0
      src/Packagist/WebBundle/Entity/Package.php
  30. 109 0
      src/Packagist/WebBundle/Entity/Tag.php
  31. 451 0
      src/Packagist/WebBundle/Entity/Version.php
  32. 35 0
      src/Packagist/WebBundle/Form/PackageType.php
  33. 46 0
      src/Packagist/WebBundle/Form/VersionType.php
  34. 22 0
      src/Packagist/WebBundle/PackagistWebBundle.php
  35. 3 0
      src/Packagist/WebBundle/Resources/config/routing.yml
  36. 281 0
      src/Packagist/WebBundle/Resources/public/css/main.css
  37. 0 0
      src/Packagist/WebBundle/Resources/public/js/main.js
  38. 19 0
      src/Packagist/WebBundle/Resources/views/Web/index.html.twig
  39. 20 0
      src/Packagist/WebBundle/Resources/views/Web/submitPackage.html.twig
  40. 51 0
      src/Packagist/WebBundle/Resources/views/Web/submitVersion.html.twig
  41. 79 0
      src/Packagist/WebBundle/Resources/views/layout.html.twig
  42. 265 0
      web/.htaccess
  43. 15 0
      web/app.php
  44. 20 0
      web/app_dev.php
  45. BIN
      web/apple-touch-icon.png
  46. BIN
      web/favicon.ico
  47. 4 0
      web/robots.txt

+ 6 - 0
.gitignore

@@ -0,0 +1,6 @@
+web/bundles/
+app/bootstrap*
+app/cache/*
+app/logs/*
+build/
+vendor/

+ 19 - 0
LICENSE

@@ -0,0 +1,19 @@
+Copyright (c) 2011 Jordi Boggiano, Nils Adermann
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is furnished
+to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.

+ 1 - 0
app/.htaccess

@@ -0,0 +1 @@
+deny from all

+ 9 - 0
app/AppCache.php

@@ -0,0 +1,9 @@
+<?php
+
+require_once __DIR__.'/AppKernel.php';
+
+use Symfony\Bundle\FrameworkBundle\HttpCache\HttpCache;
+
+class AppCache extends HttpCache
+{
+}

+ 53 - 0
app/AppKernel.php

@@ -0,0 +1,53 @@
+<?php
+
+use Symfony\Component\HttpKernel\Kernel;
+use Symfony\Component\Config\Loader\LoaderInterface;
+use Symfony\Component\ClassLoader\DebugUniversalClassLoader;
+use Symfony\Component\HttpKernel\Debug\ErrorHandler;
+use Symfony\Component\HttpKernel\Debug\ExceptionHandler;
+
+class AppKernel extends Kernel
+{
+    public function registerBundles()
+    {
+        $bundles = array(
+            new Symfony\Bundle\FrameworkBundle\FrameworkBundle(),
+            new Symfony\Bundle\SecurityBundle\SecurityBundle(),
+            new Symfony\Bundle\TwigBundle\TwigBundle(),
+            new Symfony\Bundle\MonologBundle\MonologBundle(),
+            new Symfony\Bundle\SwiftmailerBundle\SwiftmailerBundle(),
+            new Symfony\Bundle\DoctrineBundle\DoctrineBundle(),
+            new Symfony\Bundle\AsseticBundle\AsseticBundle(),
+            new Sensio\Bundle\FrameworkExtraBundle\SensioFrameworkExtraBundle(),
+            new JMS\SecurityExtraBundle\JMSSecurityExtraBundle(),
+            new Packagist\WebBundle\PackagistWebBundle(),
+        );
+
+        if (in_array($this->getEnvironment(), array('dev', 'test'))) {
+            $bundles[] = new Symfony\Bundle\WebProfilerBundle\WebProfilerBundle();
+        }
+
+        return $bundles;
+    }
+
+    public function init()
+    {
+        if ($this->debug) {
+            ini_set('display_errors', 1);
+            error_reporting(-1);
+
+            DebugUniversalClassLoader::enable();
+            ErrorHandler::register();
+            if ('cli' !== php_sapi_name()) {
+                ExceptionHandler::register();
+            }
+        } else {
+            ini_set('display_errors', 0);
+        }
+    }
+
+    public function registerContainerConfiguration(LoaderInterface $loader)
+    {
+        $loader->load(__DIR__.'/config/config_'.$this->getEnvironment().'.yml');
+    }
+}

+ 13 - 0
app/Resources/views/base.html.twig

@@ -0,0 +1,13 @@
+<!DOCTYPE html>
+<html>
+    <head>
+        <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
+        <title>{% block title %}Welcome!{% endblock %}</title>
+        {% block stylesheets %}{% endblock %}
+        <link rel="shortcut icon" href="{{ asset('favicon.ico') }}" />
+    </head>
+    <body>
+        {% block body %}{% endblock %}
+        {% block javascripts %}{% endblock %}
+    </body>
+</html>

+ 33 - 0
app/autoload.php

@@ -0,0 +1,33 @@
+<?php
+
+use Symfony\Component\ClassLoader\UniversalClassLoader;
+
+$loader = new UniversalClassLoader();
+$loader->registerNamespaces(array(
+    'Symfony'          => array(__DIR__.'/../vendor/symfony/src', __DIR__.'/../vendor/bundles'),
+    'Sensio'           => __DIR__.'/../vendor/bundles',
+    'JMS'              => __DIR__.'/../vendor/bundles',
+    'Doctrine\\Common' => __DIR__.'/../vendor/doctrine-common/lib',
+    'Doctrine\\DBAL'   => __DIR__.'/../vendor/doctrine-dbal/lib',
+    'Doctrine'         => __DIR__.'/../vendor/doctrine/lib',
+    'Monolog'          => __DIR__.'/../vendor/monolog/src',
+    'Assetic'          => __DIR__.'/../vendor/assetic/src',
+    'Metadata'         => __DIR__.'/../vendor/metadata/src',
+    'Packagist'        => __DIR__.'/../src',
+));
+$loader->registerPrefixes(array(
+    'Twig_Extensions_' => __DIR__.'/../vendor/twig-extensions/lib',
+    'Twig_'            => __DIR__.'/../vendor/twig/lib',
+));
+$loader->registerPrefixFallbacks(array(
+    __DIR__.'/../vendor/symfony/src/Symfony/Component/Locale/Resources/stubs',
+));
+$loader->registerNamespaceFallbacks(array(
+    __DIR__.'/../src',
+));
+$loader->register();
+
+// Swiftmailer needs a special autoloader to allow
+// the lazy loading of the init file (which is expensive)
+require_once __DIR__.'/../vendor/swiftmailer/lib/classes/Swift.php';
+Swift::registerAutoload(__DIR__.'/../vendor/swiftmailer/lib/swift_init.php');

+ 84 - 0
app/check.php

@@ -0,0 +1,84 @@
+<?php
+
+if (!$iniPath = get_cfg_var('cfg_file_path')) {
+    $iniPath = 'WARNING: not using a php.ini file';
+}
+
+echo "********************************\n";
+echo "*                              *\n";
+echo "*  Symfony requirements check  *\n";
+echo "*                              *\n";
+echo "********************************\n\n";
+echo sprintf("php.ini used by PHP: %s\n\n", $iniPath);
+
+echo "** WARNING **\n";
+echo "*  The PHP CLI can use a different php.ini file\n";
+echo "*  than the one used with your web server.\n";
+if ('\\' == DIRECTORY_SEPARATOR) {
+    echo "*  (especially on the Windows platform)\n";
+}
+echo "*  If this is the case, please ALSO launch this\n";
+echo "*  utility from your web server.\n";
+echo "** WARNING **\n";
+
+// mandatory
+echo_title("Mandatory requirements");
+check(version_compare(phpversion(), '5.3.2', '>='), sprintf('Checking that PHP version is at least 5.3.2 (%s installed)', phpversion()), 'Install PHP 5.3.2 or newer (current version is '.phpversion(), true);
+check(ini_get('date.timezone'), 'Checking that the "date.timezone" setting is set', 'Set the "date.timezone" setting in php.ini (like Europe/Paris)', true);
+check(is_writable(__DIR__.'/../app/cache'), sprintf('Checking that app/cache/ directory is writable'), 'Change the permissions of the app/cache/ directory so that the web server can write in it', true);
+check(is_writable(__DIR__.'/../app/logs'), sprintf('Checking that the app/logs/ directory is writable'), 'Change the permissions of the app/logs/ directory so that the web server can write in it', true);
+check(function_exists('json_encode'), 'Checking that the json_encode() is available', 'Install and enable the json extension', true);
+
+// warnings
+echo_title("Optional checks");
+check(class_exists('DomDocument'), 'Checking that the PHP-XML module is installed', 'Install and enable the php-xml module', false);
+check(defined('LIBXML_COMPACT'), 'Checking that the libxml version is at least 2.6.21', 'Upgrade your php-xml module with a newer libxml', false);
+check(function_exists('token_get_all'), 'Checking that the token_get_all() function is available', 'Install and enable the Tokenizer extension (highly recommended)', false);
+check(function_exists('mb_strlen'), 'Checking that the mb_strlen() function is available', 'Install and enable the mbstring extension', false);
+check(function_exists('iconv'), 'Checking that the iconv() function is available', 'Install and enable the iconv extension', false);
+check(function_exists('utf8_decode'), 'Checking that the utf8_decode() is available', 'Install and enable the XML extension', false);
+check(function_exists('posix_isatty'), 'Checking that the posix_isatty() is available', 'Install and enable the php_posix extension (used to colorized the CLI output)', false);
+check(class_exists('Locale'), 'Checking that the intl extension is available', 'Install and enable the intl extension (used for validators)', false);
+
+$accelerator = 
+    (function_exists('apc_store') && ini_get('apc.enabled'))
+    ||
+    function_exists('eaccelerator_put') && ini_get('eaccelerator.enable')
+    ||
+    function_exists('xcache_set')
+;
+check($accelerator, 'Checking that a PHP accelerator is installed', 'Install a PHP accelerator like APC (highly recommended)', false);
+
+check(!ini_get('short_open_tag'), 'Checking that php.ini has short_open_tag set to off', 'Set short_open_tag to off in php.ini', false);
+check(!ini_get('magic_quotes_gpc'), 'Checking that php.ini has magic_quotes_gpc set to off', 'Set magic_quotes_gpc to off in php.ini', false);
+check(!ini_get('register_globals'), 'Checking that php.ini has register_globals set to off', 'Set register_globals to off in php.ini', false);
+check(!ini_get('session.auto_start'), 'Checking that php.ini has session.auto_start set to off', 'Set session.auto_start to off in php.ini', false);
+
+echo_title("Optional checks (Doctrine)");
+
+check(class_exists('PDO'), 'Checking that PDO is installed', 'Install PDO (mandatory for Doctrine)', false);
+if (class_exists('PDO')) {
+    $drivers = PDO::getAvailableDrivers();
+    check(count($drivers), 'Checking that PDO has some drivers installed: '.implode(', ', $drivers), 'Install PDO drivers (mandatory for Doctrine)');
+}
+
+/**
+ * Checks a configuration.
+ */
+function check($boolean, $message, $help = '', $fatal = false)
+{
+    echo $boolean ? "  OK        " : sprintf("\n\n[[%s]] ", $fatal ? ' ERROR ' : 'WARNING');
+    echo sprintf("$message%s\n", $boolean ? '' : ': FAILED');
+
+    if (!$boolean) {
+        echo "            *** $help ***\n";
+        if ($fatal) {
+            die("You must fix this problem before resuming the check.\n");
+        }
+    }
+}
+
+function echo_title($title)
+{
+    echo "\n** $title **\n\n";
+}

+ 57 - 0
app/config/config.yml

@@ -0,0 +1,57 @@
+imports:
+    - { resource: parameters.ini }
+    - { resource: security.yml }
+
+framework:
+    secret:          %secret%
+    charset:         UTF-8
+    router:          { resource: "%kernel.root_dir%/config/routing.yml" }
+    form:            true
+    csrf_protection: true
+    validation:      { enable_annotations: true }
+    templating:      { engines: ['twig'] } #assets_version: SomeVersionScheme
+    session:
+        default_locale: %locale%
+        lifetime:       3600
+        auto_start:     true
+
+# Twig Configuration
+twig:
+    debug:            %kernel.debug%
+    strict_variables: %kernel.debug%
+    extensions: [twig.extension.text, twig.extension.debug]
+
+# Assetic Configuration
+assetic:
+    debug:          %kernel.debug%
+    use_controller: false
+    filters:
+        cssrewrite: ~
+        closure:
+            jar: %kernel.root_dir%/java/compiler.jar
+        yui_css:
+            jar: %kernel.root_dir%/java/yuicompressor-2.4.2.jar
+
+# Doctrine Configuration
+doctrine:
+    dbal:
+        driver:   %database_driver%
+        host:     %database_host%
+        dbname:   %database_name%
+        user:     %database_user%
+        password: %database_password%
+        charset:  UTF8
+    orm:
+        auto_generate_proxy_classes: %kernel.debug%
+        auto_mapping: true
+
+# Swiftmailer Configuration
+swiftmailer:
+    transport: %mailer_transport%
+    host:      %mailer_host%
+    username:  %mailer_user%
+    password:  %mailer_password%
+
+jms_security_extra:
+    secure_controllers:  true
+    secure_all_services: false

+ 23 - 0
app/config/config_dev.yml

@@ -0,0 +1,23 @@
+imports:
+    - { resource: config.yml }
+
+framework:
+    router:   { resource: "%kernel.root_dir%/config/routing_dev.yml" }
+    profiler: { only_exceptions: false }
+
+web_profiler:
+    toolbar: false
+    intercept_redirects: false
+
+monolog:
+    handlers:
+        main:
+            type:  stream
+            path:  %kernel.logs_dir%/%kernel.environment%.log
+            level: debug
+        firephp:
+            type:  firephp
+            level: info
+
+#assetic:
+#    use_controller: true

+ 18 - 0
app/config/config_prod.yml

@@ -0,0 +1,18 @@
+imports:
+    - { resource: config.yml }
+
+#doctrine:
+#    metadata_cache_driver: apc
+#    result_cache_driver: apc
+#    query_cache_driver: apc
+
+monolog:
+    handlers:
+        main:
+            type:         fingers_crossed
+            action_level: error
+            handler:      nested
+        nested:
+            type:  stream
+            path:  %kernel.logs_dir%/%kernel.environment%.log
+            level: debug

+ 14 - 0
app/config/config_test.yml

@@ -0,0 +1,14 @@
+imports:
+    - { resource: config_dev.yml }
+
+framework:
+    test: ~
+    session:
+        storage_id: session.storage.filesystem
+
+web_profiler:
+    toolbar: false
+    intercept_redirects: false
+
+swiftmailer:
+    disable_delivery: true

+ 18 - 0
app/config/parameters.ini

@@ -0,0 +1,18 @@
+; These parameters can be imported into other config files
+; by enclosing the key with % (like %database_user%)
+; Comments start with ';', as in php.ini
+[parameters]
+    database_driver   = pdo_mysql
+    database_host     = localhost
+    database_name     = packagist
+    database_user     = root
+    database_password =
+
+    mailer_transport  = smtp
+    mailer_host       = localhost
+    mailer_user       =
+    mailer_password   =
+
+    locale            = en
+
+    secret            = 84f4c65b9cde3417ecaeedc4fb57742c

+ 3 - 0
app/config/routing.yml

@@ -0,0 +1,3 @@
+_packagist:
+    resource: "@PackagistWebBundle/Controller"
+    type:     annotation

+ 14 - 0
app/config/routing_dev.yml

@@ -0,0 +1,14 @@
+#_assetic:
+#    resource: .
+#    type:     assetic
+
+_wdt:
+    resource: "@WebProfilerBundle/Resources/config/routing/wdt.xml"
+    prefix:   /_wdt
+
+_profiler:
+    resource: "@WebProfilerBundle/Resources/config/routing/profiler.xml"
+    prefix:   /_profiler
+
+_main:
+    resource: routing.yml

+ 41 - 0
app/config/security.yml

@@ -0,0 +1,41 @@
+security:
+    encoders:
+        Symfony\Component\Security\Core\User\User: plaintext
+
+    role_hierarchy:
+        ROLE_ADMIN:       ROLE_USER
+        ROLE_SUPER_ADMIN: [ROLE_USER, ROLE_ADMIN, ROLE_ALLOWED_TO_SWITCH]
+
+    providers:
+        in_memory:
+            users:
+                user:  { password: userpass, roles: [ 'ROLE_USER' ] }
+                admin: { password: adminpass, roles: [ 'ROLE_ADMIN' ] }
+
+    firewalls:
+        profiler:
+            pattern:  ^/_profiler
+            security: false
+
+        wdt:
+            pattern:  ^/_wdt
+            security: false
+
+        login:
+            pattern:  ^/demo/secured/login$
+            security: false
+
+        secured_area:
+            pattern:    ^/demo/secured/
+            form_login:
+                check_path: /demo/secured/login_check
+                login_path: /demo/secured/login
+            logout:
+                path:   /demo/secured/logout
+                target: /demo/
+            #anonymous: ~
+            #http_basic:
+            #    realm: "Secured Demo Area"
+
+    access_control:
+        #- { path: /login, roles: IS_AUTHENTICATED_ANONYMOUSLY, requires_channel: https }

+ 16 - 0
app/console

@@ -0,0 +1,16 @@
+#!/usr/bin/env php
+<?php
+
+require_once __DIR__.'/bootstrap.php.cache';
+require_once __DIR__.'/AppKernel.php';
+
+use Symfony\Bundle\FrameworkBundle\Console\Application;
+use Symfony\Component\Console\Input\ArgvInput;
+
+$input = new ArgvInput();
+$env = $input->getParameterOption(array('--env', '-e'), 'dev');
+$debug = !$input->hasParameterOption(array('--no-debug', ''));
+
+$kernel = new AppKernel($env, $debug);
+$application = new Application($kernel);
+$application->run();

BIN
app/java/compiler.jar


BIN
app/java/yuicompressor-2.4.2.jar


+ 41 - 0
app/phpunit.xml.dist

@@ -0,0 +1,41 @@
+<?xml version="1.0" encoding="UTF-8"?>
+
+<!-- http://www.phpunit.de/manual/current/en/appendixes.configuration.html -->
+<phpunit
+    backupGlobals               = "false"
+    backupStaticAttributes      = "false"
+    colors                      = "true"
+    convertErrorsToExceptions   = "true"
+    convertNoticesToExceptions  = "true"
+    convertWarningsToExceptions = "true"
+    processIsolation            = "false"
+    stopOnFailure               = "false"
+    syntaxCheck                 = "false"
+    bootstrap                   = "bootstrap.php.cache" >
+
+    <testsuites>
+        <testsuite name="Project Test Suite">
+            <directory>../src/*/*Bundle/Tests</directory>
+            <directory>../src/*/Bundle/*Bundle/Tests</directory>
+        </testsuite>
+    </testsuites>
+
+    <!--
+    <php>
+        <server name="KERNEL_DIR" value="/path/to/your/app/" />
+    </php>
+    -->
+
+    <filter>
+        <whitelist>
+            <directory>../src</directory>
+            <exclude>
+                <directory>../src/*/*Bundle/Resources</directory>
+                <directory>../src/*/*Bundle/Tests</directory>
+                <directory>../src/*/Bundle/*Bundle/Resources</directory>
+                <directory>../src/*/Bundle/*Bundle/Tests</directory>
+            </exclude>
+        </whitelist>
+    </filter>
+
+</phpunit>

+ 1 - 0
bin/.htaccess

@@ -0,0 +1 @@
+deny from all

+ 99 - 0
bin/build.sh

@@ -0,0 +1,99 @@
+#!/bin/sh
+
+# This file is part of the Symfony Standard Edition.
+#
+# (c) Fabien Potencier <fabien@symfony.com>
+#
+# For the full copyright and license information, please view the LICENSE
+# file that was distributed with this source code.
+
+DIR=`php -r "echo realpath(dirname(\\$_SERVER['argv'][0]));"`
+cd $DIR
+VERSION=`cat VERSION`
+
+if [ ! -d "$DIR/build" ]; then
+    mkdir -p $DIR/build
+fi
+
+$DIR/bin/build_bootstrap.php
+$DIR/app/console assets:install web/
+
+# Without vendors
+rm -rf /tmp/Symfony
+mkdir /tmp/Symfony
+cp -r app /tmp/Symfony/
+cp -r bin /tmp/Symfony/
+cp -r src /tmp/Symfony/
+cp -r web /tmp/Symfony/
+cp -r README.rst /tmp/Symfony/
+cp -r LICENSE /tmp/Symfony/
+cp -r VERSION /tmp/Symfony/
+cd /tmp/Symfony
+sudo rm -rf app/cache/* app/logs/* .git*
+chmod 777 app/cache app/logs
+
+# DS_Store cleanup
+find . -name .DS_Store | xargs rm -rf -
+
+cd ..
+# avoid the creation of ._* files
+export COPY_EXTENDED_ATTRIBUTES_DISABLE=true
+export COPYFILE_DISABLE=true
+tar zcpf $DIR/build/Symfony_Standard_$VERSION.tgz Symfony
+sudo rm -f $DIR/build/Symfony_Standard_$VERSION.zip
+zip -rq $DIR/build/Symfony_Standard_$VERSION.zip Symfony
+
+# With vendors
+cd $DIR
+rm -rf /tmp/vendor
+mkdir /tmp/vendor
+TARGET=/tmp/vendor
+
+if [ ! -d "$DIR/vendor" ]; then
+    echo "The master vendor directory does not exist"
+    exit
+fi
+
+cp -r $DIR/vendor/* $TARGET/
+
+# Assetic
+cd $TARGET/assetic && rm -rf phpunit.xml* README* tests
+
+# Doctrine ORM
+cd $TARGET/doctrine && rm -rf UPGRADE* build* bin tests tools lib/vendor
+
+# Doctrine DBAL
+cd $TARGET/doctrine-dbal && rm -rf bin build* tests lib/vendor
+
+# Doctrine Common
+cd $TARGET/doctrine-common && rm -rf build* tests lib/vendor
+
+# Swiftmailer
+cd $TARGET/swiftmailer && rm -rf CHANGES README* build* docs notes test-suite tests create_pear_package.php package*
+
+# Symfony
+cd $TARGET/symfony && rm -rf README.md phpunit.xml* tests *.sh vendor
+
+# Twig
+cd $TARGET/twig && rm -rf AUTHORS CHANGELOG README.markdown bin doc package.xml.tpl phpunit.xml* test
+
+# Twig Extensions
+cd $TARGET/twig-extensions && rm -rf README doc phpunit.xml* test
+
+# Monolog
+cd $TARGET/monolog && rm -rf README.markdown phpunit.xml* tests
+
+# Metadata
+cd $TARGET/metadata && rm -rf README.rst phpunit.xml* tests
+
+# cleanup
+find $TARGET -name .git | xargs rm -rf -
+find $TARGET -name .gitignore | xargs rm -rf -
+find $TARGET -name .gitmodules | xargs rm -rf -
+find $TARGET -name .svn | xargs rm -rf -
+
+cd /tmp/
+mv /tmp/vendor /tmp/Symfony/
+tar zcpf $DIR/build/Symfony_Standard_Vendors_$VERSION.tgz Symfony
+sudo rm -f $DIR/build/Symfony_Standard_Vendors_$VERSION.zip
+zip -rq $DIR/build/Symfony_Standard_Vendors_$VERSION.zip Symfony

+ 92 - 0
bin/build_bootstrap

@@ -0,0 +1,92 @@
+#!/usr/bin/env php
+<?php
+
+/*
+ * This file is part of the Symfony Standard Edition.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+require_once __DIR__.'/../vendor/symfony/src/Symfony/Component/ClassLoader/UniversalClassLoader.php';
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+use Symfony\Component\ClassLoader\UniversalClassLoader;
+use Symfony\Component\ClassLoader\ClassCollectionLoader;
+
+$loader = new UniversalClassLoader();
+$loader->registerNamespaces(array('Symfony' => __DIR__.'/../vendor/symfony/src'));
+$loader->register();
+
+$file = __DIR__.'/../app/bootstrap.php.cache';
+if (file_exists($file)) {
+    unlink($file);
+}
+
+ClassCollectionLoader::load(array(
+    'Symfony\\Component\\DependencyInjection\\ContainerInterface',
+    'Symfony\\Component\\DependencyInjection\\Container',
+    'Symfony\\Component\\DependencyInjection\\ContainerAwareInterface',
+    'Symfony\\Component\\DependencyInjection\\ContainerAware',
+
+    'Symfony\\Component\\HttpKernel\\Bundle\\BundleInterface',
+    'Symfony\\Component\\HttpKernel\\Bundle\\Bundle',
+    'Symfony\\Component\\HttpKernel\\Debug\\ErrorHandler',
+    'Symfony\\Component\\HttpKernel\\HttpKernelInterface',
+    'Symfony\\Component\\HttpKernel\\HttpKernel',
+    'Symfony\\Component\\HttpKernel\\KernelInterface',
+    'Symfony\\Component\\HttpKernel\\Kernel',
+
+    'Symfony\\Component\\HttpFoundation\\ParameterBag',
+    'Symfony\\Component\\HttpFoundation\\FileBag',
+    'Symfony\\Component\\HttpFoundation\\ServerBag',
+    'Symfony\\Component\\HttpFoundation\\HeaderBag',
+    'Symfony\\Component\\HttpFoundation\\Request',
+    'Symfony\\Component\\HttpFoundation\\ApacheRequest',
+
+    'Symfony\\Component\\ClassLoader\\ClassCollectionLoader',
+    'Symfony\\Component\\ClassLoader\\UniversalClassLoader',
+    'Symfony\\Component\\ClassLoader\\MapFileClassLoader',
+
+    'Symfony\\Component\\Config\\ConfigCache',
+), dirname($file), basename($file, '.php.cache'), false, false, '.php.cache');
+
+file_put_contents($file, "<?php\n\nnamespace { require_once __DIR__.'/autoload.php'; }\n\n".substr(file_get_contents($file), 5));
+
+$file = __DIR__.'/../app/bootstrap_cache.php.cache';
+if (file_exists($file)) {
+    unlink($file);
+}
+
+ClassCollectionLoader::load(array(
+    'Symfony\\Component\\HttpKernel\\KernelInterface',
+    'Symfony\\Component\\HttpKernel\\Kernel',
+    'Symfony\\Component\\HttpKernel\\HttpKernelInterface',
+    'Symfony\\Component\\HttpKernel\\HttpCache\\HttpCache',
+    'Symfony\\Component\\HttpKernel\\HttpCache\\StoreInterface',
+    'Symfony\\Component\\HttpKernel\\HttpCache\\Store',
+    'Symfony\\Component\\HttpKernel\\HttpCache\\Esi',
+
+    'Symfony\\Component\\HttpFoundation\\ParameterBag',
+    'Symfony\\Component\\HttpFoundation\\FileBag',
+    'Symfony\\Component\\HttpFoundation\\ServerBag',
+    'Symfony\\Component\\HttpFoundation\\HeaderBag',
+    'Symfony\\Component\\HttpFoundation\\Request',
+    'Symfony\\Component\\HttpFoundation\\ApacheRequest',
+    'Symfony\\Component\\HttpFoundation\\ResponseHeaderBag',
+    'Symfony\\Component\\HttpFoundation\\Response',
+
+    'Symfony\\Component\\ClassLoader\\UniversalClassLoader',
+), dirname($file), basename($file, '.php.cache'), false, false, '.php.cache');
+
+file_put_contents($file, "<?php\n\nnamespace { require_once __DIR__.'/autoload.php'; }\n\n".substr(file_get_contents($file), 5));

+ 121 - 0
bin/vendors

@@ -0,0 +1,121 @@
+#!/usr/bin/env php
+<?php
+
+/*
+ * This file is part of the Symfony Standard Edition.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+$rootDir = dirname(__DIR__);
+$vendorDir = $rootDir.'/vendor';
+
+array_shift($argv);
+if (!isset($argv[0])) {
+    die(<<<EOF
+Symfony2 vendors script management.
+
+Specify a command to run:
+
+ install: install vendors as specified in deps or deps.lock (recommended)
+ update:  update vendors to their latest versions (as specified in deps)
+
+
+EOF
+    );
+}
+
+if (!in_array($command = array_shift($argv), array('install', 'update'))) {
+    die(sprintf("Command \"%s\" does not exist.\n", $command));
+}
+
+if (!is_dir($vendorDir)) {
+    mkdir($vendorDir, 0777, true);
+}
+
+// versions
+$versions = array();
+if ('install' === $command && file_exists($rootDir.'/deps.lock')) {
+    foreach (file($rootDir.'/deps.lock') as $line) {
+        if (!trim($line)) {
+            continue;
+        }
+        $parts = array_values(array_filter(explode(' ', trim($line))));
+        if (2 !== count($parts)) {
+            die(sprintf('The deps version file is not valid (near "%s")', $line));
+        }
+        $versions[$parts[0]] = $parts[1];
+    }
+}
+
+foreach (file($rootDir.'/deps') as $line) {
+    if (!trim($line)) {
+        continue;
+    }
+    $parts = array_values(array_filter(explode(' ', trim($line))));
+    if (3 !== count($parts)) {
+        die(sprintf('The deps file is not valid (near "%s")', $line));
+    }
+    list($name, $path, $url) = $parts;
+
+    $rev = 'origin/HEAD';
+    if (false !== strpos($url, '@')) {
+        list($url, $rev) = explode('@', $url);
+    }
+
+    if (isset($versions[$name])) {
+        $rev = $versions[$name];
+    }
+
+    $installDir = $vendorDir.'/'.$path.'/'.$name;
+    if (in_array('--reinstall', $argv)) {
+        if (PHP_OS == 'WINNT') {
+            system('rmdir /S /Q "'.realpath($installDir)).'"';
+        } else {
+            system('rm -rf "'.$installDir.'"');
+        }
+    }
+
+    echo "> Installing/Updating $name\n";
+
+    if (!is_dir($installDir)) {
+        system(sprintf('git clone %s "%s"', $url, $installDir));
+    }
+
+    system(sprintf('cd "%s" && git fetch origin && git reset --hard %s', $installDir, $rev));
+}
+
+// update?
+if ('update' === $command) {
+    $deps = array();
+    foreach (file($rootDir.'/deps') as $line) {
+        if (!trim($line)) {
+            continue;
+        }
+        $parts = array_values(array_filter(explode(' ', trim($line))));
+        if (3 !== count($parts)) {
+            die(sprintf('The deps file is not valid (near "%s")', $line));
+        }
+        list($name, $path, $url) = $parts;
+
+        ob_start();
+        system('cd "'.$vendorDir.'/'.$path.'/'.$name.'"; git log -n 1 --format=%H');
+        $deps[] = trim($name.' '.ob_get_clean());
+    }
+    file_put_contents($rootDir.'/deps.lock', implode("\n", $deps));
+}
+
+// php on windows can't use the shebang line from system()
+$interpreter = PHP_OS == 'WINNT' ? 'php.exe' : '';
+
+// Update the bootstrap files
+system(sprintf('%s "%s/bin/build_bootstrap"', $interpreter, $rootDir));
+
+// Update assets
+system(sprintf('%s "%s/app/console" assets:install "%s/web/"', $interpreter, $rootDir, $rootDir));
+
+// Remove the cache
+system(sprintf('%s "%s/app/console" cache:clear --no-warmup', $interpreter, $rootDir));

+ 1 - 0
src/.htaccess

@@ -0,0 +1 @@
+deny from all

+ 62 - 0
src/Packagist/WebBundle/Command/UpdatePackagesCommand.php

@@ -0,0 +1,62 @@
+<?php
+
+/*
+ * This file is part of Packagist.
+ *
+ * (c) Jordi Boggiano <j.boggiano@seld.be>
+ *     Nils Adermann <naderman@naderman.de>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Packagist\WebBundle\Command;
+
+use Symfony\Bundle\FrameworkBundle\Command\Command;
+use Symfony\Component\Console\Input\InputInterface;
+use Symfony\Component\Console\Input\InputOption;
+use Symfony\Component\Console\Output\OutputInterface;
+use Symfony\Component\HttpKernel\KernelInterface;
+use Symfony\Component\Finder\Finder;
+
+/**
+ * @author Jordi Boggiano <j.boggiano@seld.be>
+ */
+class UpdatePackagesCommand extends Command
+{
+    /**
+     * {@inheritdoc}
+     */
+    protected function configure()
+    {
+        $this
+            ->setName('pkg:update-packages')
+            ->setDefinition(array(
+            ))
+            ->setDescription('Updates packages')
+            ->setHelp(<<<EOF
+EOF
+            )
+        ;
+    }
+
+    /**
+     * {@inheritdoc}
+     */
+    protected function execute(InputInterface $input, OutputInterface $output)
+    {
+        $em = $this->container->get('doctrine')->getEntityManager();
+
+        $qb = $em->createQueryBuilder();
+        $qb->select('p')
+            ->from('Packagist\WebBundle\Entity\Package', 'p')
+            ->where('p.status = ?1')
+            ->andWhere('p.lastUpdate IS NULL')
+            ->setParameter(1, 'active');
+
+        foreach ($qb->getQuery()->getResult() as $package) {
+            //$package->lastUpdate = new \DateTime;
+            //$em->flush();
+        }
+    }
+}

+ 49 - 0
src/Packagist/WebBundle/Controller/ApiController.php

@@ -0,0 +1,49 @@
+<?php
+
+/*
+ * This file is part of Packagist.
+ *
+ * (c) Jordi Boggiano <j.boggiano@seld.be>
+ *     Nils Adermann <naderman@naderman.de>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Packagist\WebBundle\Controller;
+
+use Symfony\Bundle\FrameworkBundle\Controller\Controller;
+use Symfony\Component\HttpFoundation\Response;
+use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
+use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
+
+/**
+ * @author Jordi Boggiano <j.boggiano@seld.be>
+ */
+class ApiController extends Controller
+{
+    /**
+     * @Template()
+     * @Route("/packages.json", name="packages")
+     */
+    public function packagesAction()
+    {
+        $version = $this->get('request')->query->get('version');
+
+        $packages = $this->get('doctrine')
+            ->getRepository('Packagist\WebBundle\Entity\Package')
+            ->findAll();
+
+        $data = '{';
+        $cnt = count($packages);
+        foreach ($packages as $idx => $package) {
+            $data .= '"'.$package->getName().'":'.$package->toJson();
+            if ($cnt > $idx+1) {
+                $data .= ',';
+            }
+        }
+        $data .= '}';
+
+        return new Response($data, 200, array('Content-Type' => 'application/json'));
+    }
+}

+ 125 - 0
src/Packagist/WebBundle/Controller/WebController.php

@@ -0,0 +1,125 @@
+<?php
+
+/*
+ * This file is part of Packagist.
+ *
+ * (c) Jordi Boggiano <j.boggiano@seld.be>
+ *     Nils Adermann <naderman@naderman.de>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Packagist\WebBundle\Controller;
+
+use Symfony\Bundle\FrameworkBundle\Controller\Controller;
+use Packagist\WebBundle\Entity\Package;
+use Packagist\WebBundle\Entity\Version;
+use Packagist\WebBundle\Form\PackageType;
+use Packagist\WebBundle\Form\VersionType;
+use Symfony\Component\HttpFoundation\RedirectResponse;
+use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
+use Sensio\Bundle\FrameworkExtraBundle\Configuration\Template;
+use Sensio\Bundle\FrameworkExtraBundle\Configuration\Route;
+
+/**
+ * @author Jordi Boggiano <j.boggiano@seld.be>
+ */
+class WebController extends Controller
+{
+    /**
+     * @Template()
+     * @Route("/", name="home")
+     */
+    public function indexAction()
+    {
+        $packages = $this->get('doctrine')
+            ->getRepository('Packagist\WebBundle\Entity\Package')
+            ->findAll();
+
+        return array('packages' => $packages, 'page' => 'home');
+    }
+
+    /**
+     * @Template()
+     * @Route("/submit", name="submit")
+     */
+    public function submitPackageAction()
+    {
+        $package = new Package;
+        $form = $this->get('form.factory')->create(new PackageType, $package);
+
+        $request = $this->get('request');
+        if ($request->getMethod() == 'POST') {
+            $form->bindRequest($request);
+            if ($form->isValid()) {
+                try {
+                    $em = $this->get('doctrine')->getEntityManager();
+                    $em->persist($package);
+                    $em->flush();
+
+                    $this->get('session')->setFlash('success', $package->getName().' has been added to the package list, now go ahead and add a release!');
+                    return new RedirectResponse($this->generateUrl('submit_version', array('package' => $package->getName())));
+                } catch (\PDOExceptionx $e) {
+                    $this->get('session')->setFlash('error', $package->getName().' could not be saved in our database, most likely the name is already in use.');
+                }
+            }
+        }
+
+        return array('form' => $form->createView(), 'page' => 'submit');
+    }
+
+    /**
+     * @Template()
+     * @Route("/submit/{package}", name="submit_version")
+     */
+    public function submitVersionAction($package)
+    {
+        $em = $this->get('doctrine')->getEntityManager();
+
+        $pkg = $this->get('doctrine')->getRepository('Packagist\WebBundle\Entity\Package')
+            ->findOneByName($package);
+
+        if (!$pkg) {
+            throw new NotFoundHttpException('Package '.$package.' not found.');
+        }
+
+        // TODO populate with the latest version's data
+        $version = new Version;
+        $version->setEntityManager($em);
+        $version->setName($pkg->getName());
+        $version->setDescription($pkg->getDescription());
+        $form = $this->get('form.factory')->create(new VersionType, $version);
+
+        $request = $this->get('request');
+        if ($request->getMethod() == 'POST') {
+            $form->bindRequest($request);
+
+            if ($form->isValid()) {
+                try {
+                    // TODO check if this is the latest version to move the latest dist-tags reference, and update the package's description perhaps
+                    $pkg->addVersions($version);
+                    $version->setPackage($pkg);
+                    $em->persist($version);
+                    $em->flush();
+
+                    $this->get('session')->setFlash('success', $pkg->getName().'\'s version '.$version->getVersion().' has been added.');
+                    return new RedirectResponse($this->generateUrl('home'));
+                } catch (\PDOException $e) {
+                    $this->get('session')->setFlash('error', $pkg->getName().'\'s version '.$version->getVersion().' could not be saved in our database, most likely it already exists.');
+                }
+            }
+        }
+
+        return array('form' => $form->createView(), 'package' => $pkg, 'page' => 'submit');
+    }
+
+    /**
+     * @Template()
+     * @Route("/about", name="about")
+     */
+    public function aboutAction()
+    {
+        return array();
+    }
+}

+ 201 - 0
src/Packagist/WebBundle/Entity/Package.php

@@ -0,0 +1,201 @@
+<?php
+
+/*
+ * This file is part of Packagist.
+ *
+ * (c) Jordi Boggiano <j.boggiano@seld.be>
+ *     Nils Adermann <naderman@naderman.de>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Packagist\WebBundle\Entity;
+
+use Doctrine\ORM\Mapping as ORM;
+use Symfony\Component\Validator\Constraints as Assert;
+
+/**
+ * @ORM\Entity
+ * @ORM\Table(
+ *     name="package",
+ *     uniqueConstraints={@ORM\UniqueConstraint(name="name_idx",columns={"name"})}
+ * )
+ * @author Jordi Boggiano <j.boggiano@seld.be>
+ */
+class Package
+{
+    /**
+     * @ORM\Id
+     * @ORM\Column(type="integer")
+     * @ORM\GeneratedValue(strategy="AUTO")
+     */
+    private $id;
+
+    /**
+     * Unique package name
+     *
+     * @ORM\Column
+     * @Assert\NotBlank()
+     */
+    private $name;
+
+    /**
+     * @ORM\Column(type="text", nullable="true")
+     */
+    private $description;
+
+    /**
+     * @ORM\OneToMany(targetEntity="Packagist\WebBundle\Entity\Version",mappedBy="package")
+     */
+    private $versions;
+
+//    /**
+//     * @ORM\ManyToMany(targetEntity="User")
+//     */
+//    private $maintainers;
+
+    // dist-tags / rel or runtime?
+
+    /**
+     * @ORM\Column(type="datetime")
+     */
+    private $createdAt;
+
+    /**
+     * @ORM\Column(type="datetime", nullable="true")
+     */
+    private $updatedAt;
+
+    public function __construct()
+    {
+        $this->versions = new \Doctrine\Common\Collections\ArrayCollection();
+        $this->createdAt = new \DateTime;
+    }
+
+    public function toJson()
+    {
+        $versions = array();
+        foreach ($this->versions as $version) {
+            $versions[$version->getVersion()] = $version->toArray();
+        }
+        $data = array(
+            'name' => $this->name,
+            'description' => $this->description,
+            'dist-tags' => array(),
+            'maintainers' => array(),
+            'versions' => $versions,
+        );
+        return json_encode($data);
+    }
+
+    /**
+     * Get id
+     *
+     * @return string $id
+     */
+    public function getId()
+    {
+        return $this->id;
+    }
+
+    /**
+     * Set name
+     *
+     * @param string $name
+     */
+    public function setName($name)
+    {
+        $this->name = $name;
+    }
+
+    /**
+     * Get name
+     *
+     * @return string $name
+     */
+    public function getName()
+    {
+        return $this->name;
+    }
+
+    /**
+     * Set description
+     *
+     * @param text $description
+     */
+    public function setDescription($description)
+    {
+        $this->description = $description;
+    }
+
+    /**
+     * Get description
+     *
+     * @return text $description
+     */
+    public function getDescription()
+    {
+        return $this->description;
+    }
+
+    /**
+     * Set createdAt
+     *
+     * @param datetime $createdAt
+     */
+    public function setCreatedAt($createdAt)
+    {
+        $this->createdAt = $createdAt;
+    }
+
+    /**
+     * Get createdAt
+     *
+     * @return datetime $createdAt
+     */
+    public function getCreatedAt()
+    {
+        return $this->createdAt;
+    }
+
+    /**
+     * Add versions
+     *
+     * @param Packagist\WebBundle\Entity\Version $versions
+     */
+    public function addVersions(\Packagist\WebBundle\Entity\Version $versions)
+    {
+        $this->versions[] = $versions;
+    }
+
+    /**
+     * Get versions
+     *
+     * @return string $versions
+     */
+    public function getVersions()
+    {
+        return $this->versions;
+    }
+
+    /**
+     * Set updatedAt
+     *
+     * @param datetime $updatedAt
+     */
+    public function setUpdatedAt($updatedAt)
+    {
+        $this->updatedAt = $updatedAt;
+    }
+
+    /**
+     * Get updatedAt
+     *
+     * @return datetime $updatedAt
+     */
+    public function getUpdatedAt()
+    {
+        return $this->updatedAt;
+    }
+}

+ 109 - 0
src/Packagist/WebBundle/Entity/Tag.php

@@ -0,0 +1,109 @@
+<?php
+
+/*
+ * This file is part of Packagist.
+ *
+ * (c) Jordi Boggiano <j.boggiano@seld.be>
+ *     Nils Adermann <naderman@naderman.de>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Packagist\WebBundle\Entity;
+
+use Doctrine\ORM\Mapping as ORM;
+use Symfony\Component\Validator\Constraints as Assert;
+
+/**
+ * @ORM\Entity
+ * @ORM\Table(name="tag")
+ * @author Jordi Boggiano <j.boggiano@seld.be>
+ */
+class Tag
+{
+    /**
+     * @ORM\Id
+     * @ORM\Column(type="integer")
+     * @ORM\GeneratedValue(strategy="AUTO")
+     */
+    private $id;
+
+    /**
+     * @ORM\Column
+     * @Assert\NotBlank()
+     */
+    private $name;
+
+    /**
+     * @ORM\ManyToMany(targetEntity="Packagist\WebBundle\Entity\Version", mappedBy="tags")
+     */
+    private $versions;
+
+    public function __construct($name = null)
+    {
+        $this->name = $name;
+    }
+
+    public static function getByName($em, $name, $create = false)
+    {
+        try {
+            $qb = $em->createQueryBuilder();
+            $qb->select('t')
+                ->from(__CLASS__, 't')
+                ->where('t.name = ?1')
+                ->setMaxResults(1)
+                ->setParameter(1, $name);
+            return $qb->getQuery()->getSingleResult();
+        } catch (\Doctrine\ORM\NoResultException $e) {
+        }
+        $tag = new self($name);
+        $em->persist($tag);
+        return $tag;
+    }
+
+    public function setId($id)
+    {
+        $this->id = $id;
+    }
+
+    public function getId()
+    {
+        return $this->id;
+    }
+
+    public function setName($name)
+    {
+        $this->name = $name;
+    }
+
+    public function getName()
+    {
+        return $this->name;
+    }
+
+    /**
+     * Add versions
+     *
+     * @param Packagist\WebBundle\Entity\Version $versions
+     */
+    public function addVersions(\Packagist\WebBundle\Entity\Version $versions)
+    {
+        $this->versions[] = $versions;
+    }
+
+    /**
+     * Get versions
+     *
+     * @return Doctrine\Common\Collections\Collection $versions
+     */
+    public function getVersions()
+    {
+        return $this->versions;
+    }
+
+    public function __toString()
+    {
+        return $this->name;
+    }
+}

+ 451 - 0
src/Packagist/WebBundle/Entity/Version.php

@@ -0,0 +1,451 @@
+<?php
+
+/*
+ * This file is part of Packagist.
+ *
+ * (c) Jordi Boggiano <j.boggiano@seld.be>
+ *     Nils Adermann <naderman@naderman.de>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Packagist\WebBundle\Entity;
+
+use Doctrine\ORM\Mapping as ORM;
+use Symfony\Component\Validator\Constraints as Assert;
+
+/**
+ * @ORM\Entity
+ * @ORM\Table(
+ *     name="package_version",
+ *     uniqueConstraints={@ORM\UniqueConstraint(name="pkg_ver_idx",columns={"package_id","version"})}
+ * )
+ * @author Jordi Boggiano <j.boggiano@seld.be>
+ */
+class Version
+{
+    /**
+     * @ORM\Id
+     * @ORM\Column(type="integer")
+     * @ORM\GeneratedValue(strategy="AUTO")
+     */
+    private $id;
+
+    /**
+     * @ORM\Column
+     * @Assert\NotBlank()
+     */
+    private $name;
+
+    /**
+     * @ORM\Column(type="text", nullable="true")
+     */
+    private $description;
+
+    /**
+     * @ORM\ManyToMany(targetEntity="Packagist\WebBundle\Entity\Tag", inversedBy="versions")
+     * @ORM\JoinTable(name="version_tag",
+     *     joinColumns={@ORM\JoinColumn(name="version_id", referencedColumnName="id")},
+     *     inverseJoinColumns={@ORM\JoinColumn(name="tag_id", referencedColumnName="id")}
+     * )
+     */
+    private $tags;
+
+    /**
+     * @ORM\ManyToOne(targetEntity="Packagist\WebBundle\Entity\Package", fetch="EAGER", inversedBy="versions")
+     * @Assert\Type(type="Packagist\WebBundle\Entity\Package")
+     */
+    private $package;
+
+    /**
+     * @ORM\Column(nullable="true")
+     * @Assert\Url()
+     */
+    private $homepage;
+
+    /**
+     * @ORM\Column
+     * @Assert\NotBlank()
+     */
+    private $version;
+
+    /**
+     * @ORM\Column(nullable="true")
+     */
+    private $license;
+
+//    /**
+//     * @ORM\ManyToMany(targetEntity="User")
+//     */
+//    private $authors;
+
+    /**
+     * JSON object of source spec
+     *
+     * @ORM\Column(type="text")
+     * @Assert\NotBlank()
+     */
+    private $source;
+
+    /**
+     * JSON object of requirements
+     *
+     * @ORM\Column(type="text", name="requires")
+     * @Assert\NotBlank()
+     */
+    private $require;
+
+//    dist (later)
+
+    /**
+     * @ORM\Column(type="datetime")
+     */
+    private $createdAt;
+
+    /**
+     * @ORM\Column(type="datetime")
+     */
+    private $updatedAt;
+
+    /**
+     * @ORM\Column(type="datetime")
+     * @Assert\NotBlank()
+     */
+    private $releasedAt;
+
+    public function __construct()
+    {
+        $this->tags = new \Doctrine\Common\Collections\ArrayCollection();
+        $this->createdAt = new \DateTime;
+        $this->updatedAt = new \DateTime;
+    }
+
+    public function toArray()
+    {
+        $tags = array();
+        foreach ($this->tags as $tag) {
+            $tags[] = $tag->getName();
+        }
+        return array(
+            'name' => $this->name,
+            'description' => $this->description,
+            'keywords' => $tags,
+            'homepage' => $this->homepage,
+            'version' => $this->version,
+            'license' => $this->license,
+            'authors' => array(),
+            'require' => $this->getRequire(),
+            'source' => $this->getSource(),
+            'time' => $this->releasedAt->format('Y-m-d\TH:i:s'),
+            'dist' => array(),
+        );
+    }
+
+    /**
+     * Get id
+     *
+     * @return string $id
+     */
+    public function getId()
+    {
+        return $this->id;
+    }
+
+    /**
+     * Set name
+     *
+     * @param string $name
+     */
+    public function setName($name)
+    {
+        $this->name = $name;
+    }
+
+    /**
+     * Get name
+     *
+     * @return string $name
+     */
+    public function getName()
+    {
+        return $this->name;
+    }
+
+    /**
+     * Set description
+     *
+     * @param text $description
+     */
+    public function setDescription($description)
+    {
+        $this->description = $description;
+    }
+
+    /**
+     * Get description
+     *
+     * @return text $description
+     */
+    public function getDescription()
+    {
+        return $this->description;
+    }
+
+    /**
+     * Set homepage
+     *
+     * @param string $homepage
+     */
+    public function setHomepage($homepage)
+    {
+        $this->homepage = $homepage;
+    }
+
+    /**
+     * Get homepage
+     *
+     * @return string $homepage
+     */
+    public function getHomepage()
+    {
+        return $this->homepage;
+    }
+
+    /**
+     * Set version
+     *
+     * @param string $version
+     */
+    public function setVersion($version)
+    {
+        $this->version = ltrim($version, 'vV.');
+    }
+
+    /**
+     * Get version
+     *
+     * @return string $version
+     */
+    public function getVersion()
+    {
+        return $this->version;
+    }
+
+    /**
+     * Set license
+     *
+     * @param string $license
+     */
+    public function setLicense($license)
+    {
+        $this->license = $license;
+    }
+
+    /**
+     * Get license
+     *
+     * @return string $license
+     */
+    public function getLicense()
+    {
+        return $this->license;
+    }
+
+    /**
+     * Set source
+     *
+     * @param text $source
+     */
+    public function setSource($source)
+    {
+        if (preg_match('#^([a-z-]+) (\S+)$#', $source, $m)) {
+            $this->source = json_encode(array('type' => $m[1], 'url' => $m[2]));
+        }
+    }
+
+    /**
+     * Get source
+     *
+     * @return text $source
+     */
+    public function getSource()
+    {
+        return json_decode($this->source);
+    }
+
+    /**
+     * Set require
+     *
+     * @param text $require
+     */
+    public function setRequire($require)
+    {
+        if (preg_match_all('#^(\S+) (\S+)\r?\n?$#m', $require, $m)) {
+            $requires = array();
+            foreach ($m[1] as $idx => $package) {
+                $requires[$package] = $m[2][$idx];
+            }
+            $this->require = json_encode($requires);
+        }
+    }
+
+    /**
+     * Get require
+     *
+     * @return text $require
+     */
+    public function getRequire()
+    {
+        return json_decode($this->require);
+    }
+
+    /**
+     * Set createdAt
+     *
+     * @param datetime $createdAt
+     */
+    public function setCreatedAt($createdAt)
+    {
+        $this->createdAt = $createdAt;
+    }
+
+    /**
+     * Get createdAt
+     *
+     * @return datetime $createdAt
+     */
+    public function getCreatedAt()
+    {
+        return $this->createdAt;
+    }
+
+    /**
+     * Set releasedAt
+     *
+     * @param datetime $releasedAt
+     */
+    public function setReleasedAt($releasedAt)
+    {
+        $this->releasedAt = $releasedAt;
+    }
+
+    /**
+     * Get releasedAt
+     *
+     * @return datetime $releasedAt
+     */
+    public function getReleasedAt()
+    {
+        return $this->releasedAt;
+    }
+
+    /**
+     * Set package
+     *
+     * @param Packagist\WebBundle\Entity\Package $package
+     */
+    public function setPackage(\Packagist\WebBundle\Entity\Package $package)
+    {
+        $this->package = $package;
+    }
+
+    /**
+     * Get package
+     *
+     * @return Packagist\WebBundle\Entity\Package $package
+     */
+    public function getPackage()
+    {
+        return $this->package;
+    }
+
+    /**
+     * Add tags
+     *
+     * @param Packagist\WebBundle\Entity\Tag $tags
+     */
+    public function addTags(\Packagist\WebBundle\Entity\Tag $tags)
+    {
+        $this->tags[] = $tags;
+    }
+
+    /**
+     * Get tags
+     *
+     * @return Doctrine\Common\Collections\Collection $tags
+     */
+    public function getTags()
+    {
+        return $this->tags;
+    }
+
+    public function setTagsText($text)
+    {
+        $tags = array();
+        if (trim($text)) {
+            $tags = preg_split('#[\s,]+#', trim($text));
+            $tags = array_map(function($el) {
+                return trim(ltrim($el, '#'), '"\'');
+            }, $tags);
+            $uniqueTags = array();
+            foreach ($tags as $tag) {
+                if ($tag && !isset($uniqueTags[strtolower($tag)])) {
+                    $uniqueTags[strtolower($tag)] = $tag;
+                }
+            }
+            $tags = array_values($uniqueTags);
+        }
+
+        foreach ($this->tags as $k => $tag) {
+            if (false !== ($idx = array_search($tag->getName(), $tags))) {
+                unset($tags[$idx]);
+            } else {
+                unset($this->tags[$k]);
+            }
+        }
+
+        foreach ($tags as $tag) {
+            $this->addTags($this->getTagEntity($tag));
+        }
+    }
+
+    public function setEntityManager($em)
+    {
+        $this->em = $em;
+    }
+
+    protected function getTagEntity($name)
+    {
+        return Tag::getByName($this->em, $name, true);
+    }
+
+    public function getTagsText()
+    {
+        $tags = array();
+        foreach ($this->tags as $tag) {
+            $tags[] = $tag->getName();
+        }
+        return implode(', ', $tags);
+    }
+
+    /**
+     * Set updatedAt
+     *
+     * @param datetime $updatedAt
+     */
+    public function setUpdatedAt($updatedAt)
+    {
+        $this->updatedAt = $updatedAt;
+    }
+
+    /**
+     * Get updatedAt
+     *
+     * @return datetime $updatedAt
+     */
+    public function getUpdatedAt()
+    {
+        return $this->updatedAt;
+    }
+}

+ 35 - 0
src/Packagist/WebBundle/Form/PackageType.php

@@ -0,0 +1,35 @@
+<?php
+
+/*
+ * This file is part of Packagist.
+ *
+ * (c) Jordi Boggiano <j.boggiano@seld.be>
+ *     Nils Adermann <naderman@naderman.de>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Packagist\WebBundle\Form;
+
+use Symfony\Component\Form\AbstractType;
+use Symfony\Component\Form\FormBuilder;
+
+/**
+ * @author Jordi Boggiano <j.boggiano@seld.be>
+ */
+class PackageType extends AbstractType
+{
+    public function buildForm(FormBuilder $builder, array $options)
+    {
+        $builder->add('name');
+        $builder->add('description', null, array('required' => false));
+    }
+
+    public function getDefaultOptions(array $options)
+    {
+        return array(
+            'data_class' => 'Packagist\WebBundle\Entity\Package',
+        );
+    }
+}

+ 46 - 0
src/Packagist/WebBundle/Form/VersionType.php

@@ -0,0 +1,46 @@
+<?php
+
+/*
+ * This file is part of Packagist.
+ *
+ * (c) Jordi Boggiano <j.boggiano@seld.be>
+ *     Nils Adermann <naderman@naderman.de>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Packagist\WebBundle\Form;
+
+use Symfony\Component\Form\AbstractType;
+use Symfony\Component\Form\FormBuilder;
+use Symfony\Component\Form\Extension\Core\DataTransformer\DateTimeToStringTransformer;
+
+/**
+ * @author Jordi Boggiano <j.boggiano@seld.be>
+ */
+class VersionType extends AbstractType
+{
+    public function buildForm(FormBuilder $builder, array $options)
+    {
+        $builder->add('description');
+        $builder->add('version', null);
+        $builder->add('homepage', 'url', array('required' => false));
+        $builder->add('tagsText', 'text');
+        $builder->add('license', null, array('required' => false));
+        $builder->add('source', 'text', array('required' => false));
+        $builder->add('require', null, array('required' => false));
+        $builder->add('releasedAt', 'datetime', array('date_widget' => 'text', 'time_widget' => 'text'));
+        $builder->add(
+            $builder->create('releasedAt', 'text')
+                ->appendClientTransformer(new DateTimeToStringTransformer(null, null, 'Y-m-d H:i:s'))
+        );
+    }
+
+    public function getDefaultOptions(array $options)
+    {
+        return array(
+            'data_class' => 'Packagist\WebBundle\Entity\Version',
+        );
+    }
+}

+ 22 - 0
src/Packagist/WebBundle/PackagistWebBundle.php

@@ -0,0 +1,22 @@
+<?php
+
+/*
+ * This file is part of Packagist.
+ *
+ * (c) Jordi Boggiano <j.boggiano@seld.be>
+ *     Nils Adermann <naderman@naderman.de>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Packagist\WebBundle;
+
+use Symfony\Component\HttpKernel\Bundle\Bundle;
+
+/**
+ * @author Jordi Boggiano <j.boggiano@seld.be>
+ */
+class PackagistWebBundle extends Bundle
+{
+}

+ 3 - 0
src/Packagist/WebBundle/Resources/config/routing.yml

@@ -0,0 +1,3 @@
+#homepage:
+#    pattern:  /
+#    defaults: { _controller: PackagistWebBundle:Default:index }

+ 281 - 0
src/Packagist/WebBundle/Resources/public/css/main.css

@@ -0,0 +1,281 @@
+/*  HTML5 ✰ Boilerplate  */
+
+html, body, div, span, object, iframe,
+h1, h2, h3, h4, h5, h6, p, blockquote, pre,
+abbr, address, cite, code, del, dfn, em, img, ins, kbd, q, samp,
+small, strong, sub, sup, var, b, i, dl, dt, dd, ol, ul, li,
+fieldset, form, label, legend,
+table, caption, tbody, tfoot, thead, tr, th, td,
+article, aside, canvas, details, figcaption, figure,
+footer, header, hgroup, menu, nav, section, summary,
+time, mark, audio, video {
+  margin: 0;
+  padding: 0;
+  border: 0;
+  font-size: 100%;
+  font: inherit;
+  vertical-align: baseline;
+}
+
+article, aside, details, figcaption, figure,
+footer, header, hgroup, menu, nav, section {
+  display: block;
+}
+
+blockquote, q { quotes: none; }
+blockquote:before, blockquote:after,
+q:before, q:after { content: ''; content: none; }
+ins { background-color: #ff9; color: #000; text-decoration: none; }
+mark { background-color: #ff9; color: #000; font-style: italic; font-weight: bold; }
+del { text-decoration: line-through; }
+abbr[title], dfn[title] { border-bottom: 1px dotted; cursor: help; }
+table { border-collapse: collapse; border-spacing: 0; }
+hr { display: block; height: 1px; border: 0; border-top: 1px solid #ccc; margin: 1em 0; padding: 0; }
+input, select { vertical-align: middle; }
+
+body { font:13px/1.231 sans-serif; *font-size:small; }
+select, input, textarea, button { font:99% sans-serif; }
+pre, code, kbd, samp { font-family: monospace, sans-serif; }
+
+html { overflow-y: scroll; }
+a:hover, a:active { outline: none; }
+ul, ol { margin-left: 2em; }
+ol { list-style-type: decimal; }
+nav ul, nav li { margin: 0; list-style:none; list-style-image: none; }
+small { font-size: 85%; }
+strong, th { font-weight: bold; }
+td { vertical-align: top; }
+
+sub, sup { font-size: 75%; line-height: 0; position: relative; }
+sup { top: -0.5em; }
+sub { bottom: -0.25em; }
+
+pre { white-space: pre; white-space: pre-wrap; word-wrap: break-word; padding: 15px; }
+textarea { overflow: auto; }
+.ie6 legend, .ie7 legend { margin-left: -7px; }
+input[type="radio"] { vertical-align: text-bottom; }
+input[type="checkbox"] { vertical-align: bottom; }
+.ie7 input[type="checkbox"] { vertical-align: baseline; }
+.ie6 input { vertical-align: text-bottom; }
+label, input[type="button"], input[type="submit"], input[type="image"], button { cursor: pointer; }
+button, input, select, textarea { margin: 0; }
+.no-boxshadow input:invalid, .no-boxshadow textarea:invalid { background-color: #f0dddd; }
+
+::-moz-selection{ background: #ffba53; color:#000; text-shadow: none; }
+::selection { background:#ffba53; color:#000; text-shadow: none; }
+a:link { -webkit-tap-highlight-color: #ffba53; }
+
+button {  width: auto; overflow: visible; }
+.ie7 img { -ms-interpolation-mode: bicubic; }
+
+body, select, input, textarea {  color: #444; }
+h1, h2, h3, h4, h5, h6 { font-weight: bold; }
+
+/*
+    // ========================================== \\
+   ||                                              ||
+   ||               Your styles !                  ||
+   ||                                              ||
+    \\ ========================================== //
+*/
+
+body {
+  background: #888;
+  font-size: 15px;
+  font-family: "PT Sans", sans-serif;
+  color: #555;
+}
+
+a, a:active, a:visited {
+  color: #fb9700;
+  text-decoration: none;
+}
+a:hover {
+  color: #c67700;
+}
+
+.container {
+  width: 900px;
+  background: #fff;
+  margin: auto;
+  padding: 20px 40px 40px;
+}
+
+header h1 {
+  font-size: 50px;
+  margin-top: 0;
+  font-family: "Neuton", serif;
+}
+
+header h2 {
+  font-size: 30px;
+  margin: 10px 0;
+}
+
+header p {
+  font-size: 20px;
+}
+
+header a {
+  text-shadow: 0 1px 0 #bf7300;
+}
+
+header a:hover {
+  text-shadow: 0 1px 0 #663e00;
+}
+
+header {
+  margin-bottom: 20px;
+  font-size: 15px;
+}
+
+.flash-message {
+  font-size: 20px;
+  margin: 20px 0;
+}
+
+.flash-message.success {
+  color: #519f1c;
+}
+.flash-message.error {
+  color: #a21a1a;
+}
+
+.betawarn {
+  border: 1px solid #cc0;
+  background: #ffa;
+  padding: 10px;
+  border-radius: 5px;
+}
+
+.submit, .submit:active, .submit:visited, input[type="submit"] {
+  font-size: 22px;
+  font-family: "Neuton";
+  float: right;
+  background: #64c523;
+  display: block;
+  padding: 9px 20px 12px;
+  color: #fff;
+  margin-top: 10px;
+  text-decoration: none;
+  -webkit-border-radius: 5px;
+  -moz-border-radius: 5px;
+  border-radius: 5px;
+  border: 1px solid #519f1c;
+  box-shadow: 0 0 5px rgba(0, 0, 0, .2);
+}
+
+.submit:hover {
+  color: #fff;
+  background: #53a51d;
+}
+
+.main h1 {
+  font-size: 30px;
+}
+
+.main h2 {
+  font-size: 23px;
+}
+
+label {
+  display: block;
+  margin: 20px 0 10px;
+}
+
+input, textarea {
+  width: 400px;
+}
+
+textarea {
+  resize: vertical;
+}
+
+input[type="submit"] {
+  width: 406px;
+  float: none;
+}
+
+form ul {
+  color: #c00;
+  list-style: none;
+  margin: 10px 0;
+}
+
+.package {
+  margin: 15px 0;
+}
+.package h2 {
+  float: left;
+  font-size: 25px;
+  margin-bottom: 5px;
+}
+.package .summary {
+  font-weight: normal;
+  font-size: 20px;
+  position: relative;
+  left: 10px;
+  top: 5px;
+}
+.package .description {
+  clear: left;
+}
+.package .links a {
+  display: block;
+}
+
+pre {
+  background: #ddd;
+  -moz-border-radius: 5px;
+  -webkit-border-radius: 5px;
+  border-radius: 5px;
+  display: block;
+  padding: 5px;
+  margin: 10px 0;
+}
+
+/*
+    // ========================================== \\
+   ||                                              ||
+   ||                    Finito !                  ||
+   ||                                              ||
+    \\ ========================================== //
+*/
+
+.ir { display: block; text-indent: -999em; overflow: hidden; background-repeat: no-repeat; text-align: left; direction: ltr; }
+.hidden { display: none; visibility: hidden; }
+.visuallyhidden { border: 0; clip: rect(0 0 0 0); height: 1px; margin: -1px; overflow: hidden; padding: 0; position: absolute; width: 1px; }
+.visuallyhidden.focusable:active,
+.visuallyhidden.focusable:focus { clip: auto; height: auto; margin: 0; overflow: visible; position: static; width: auto; }
+.invisible { visibility: hidden; }
+.clearfix:before, .clearfix:after { content: "\0020"; display: block; height: 0; overflow: hidden; }
+.clearfix:after { clear: both; }
+.clearfix { zoom: 1; }
+
+@media all and (orientation:portrait) {
+
+}
+
+@media all and (orientation:landscape) {
+
+}
+
+@media screen and (max-device-width: 480px) {
+
+  /* html { -webkit-text-size-adjust:none; -ms-text-size-adjust:none; } */
+}
+
+@media print {
+  * { background: transparent !important; color: black !important; text-shadow: none !important; filter:none !important;
+  -ms-filter: none !important; }
+  a, a:visited { color: #444 !important; text-decoration: underline; }
+  a[href]:after { content: " (" attr(href) ")"; }
+  abbr[title]:after { content: " (" attr(title) ")"; }
+  .ir a:after, a[href^="javascript:"]:after, a[href^="#"]:after { content: ""; }
+  pre, blockquote { border: 1px solid #999; page-break-inside: avoid; }
+  thead { display: table-header-group; }
+  tr, img { page-break-inside: avoid; }
+  @page { margin: 0.5cm; }
+  p, h2, h3 { orphans: 3; widows: 3; }
+  h2, h3{ page-break-after: avoid; }
+}

+ 0 - 0
src/Packagist/WebBundle/Resources/public/js/main.js


+ 19 - 0
src/Packagist/WebBundle/Resources/views/Web/index.html.twig

@@ -0,0 +1,19 @@
+{% extends "PackagistWebBundle::layout.html.twig" %}
+
+{% block content %}
+    <h1>Packages</h1>
+    {% for package in packages %}
+        <section class="package">
+            <h2>{{ package.name }}</h2>
+            <p class="description">{{ package.description }}</p>
+            {% if package.versions[0] is defined %}
+                <p class="license">License: {{ package.versions[0].license|default("Unknown") }}</p>
+                <p class="links">
+                    {% if package.versions[0].homepage %}
+                        <a href="{{ package.versions[0].homepage }}">{{ package.versions[0].homepage|replace({'http://': ''}) }}</a>
+                    {% endif %}
+                </p>
+            {% endif %}
+        </section>
+    {% endfor %}
+{% endblock %}

+ 20 - 0
src/Packagist/WebBundle/Resources/views/Web/submitPackage.html.twig

@@ -0,0 +1,20 @@
+{% extends "PackagistWebBundle::layout.html.twig" %}
+
+{% block content %}
+    <h1>Submit package</h1>
+    <form action="" method="POST" {{ form_enctype(form) }}>
+        {{ form_errors(form) }}
+        <p>
+            {{ form_label(form.name, "Unique Package Name") }}
+            {{ form_errors(form.name) }}
+            {{ form_widget(form.name) }}
+        </p>
+        <p>
+            {{ form_label(form.description, "Description") }}
+            {{ form_errors(form.description) }}
+            {{ form_widget(form.description) }}
+        </p>
+        {{ form_rest(form) }}
+        <input type="submit" value="Submit" />
+    </form>
+{% endblock %}

+ 51 - 0
src/Packagist/WebBundle/Resources/views/Web/submitVersion.html.twig

@@ -0,0 +1,51 @@
+{% extends "PackagistWebBundle::layout.html.twig" %}
+
+{% block content %}
+    <h1>Submit a new {{ package.name }} version</h1>
+    <form action="" method="POST" {{ form_enctype(form) }}>
+        {{ form_errors(form) }}
+        <p>
+            {{ form_label(form.version, "Version (x.y.x, optionally suffixed with -dev, -beta, -alpha or RC + an optional number)") }}
+            {{ form_errors(form.version) }}
+            {{ form_widget(form.version) }}
+        </p>
+        <p>
+            {# TODO: plug a jQuery calendar on top of this #}
+            {{ form_label(form.releasedAt, "Release Date (YYYY-MM-DD)") }}
+            {{ form_errors(form.releasedAt) }}
+            {{ form_widget(form.releasedAt) }}
+        </p>
+        <p>
+            {{ form_label(form.source, "Source (<type (git, svn)> <url>)") }}
+            {{ form_errors(form.source) }}
+            {{ form_widget(form.source) }}
+        </p>
+        <p>
+            {{ form_label(form.require, "Required packages (<package name> <version spec>, one per line)") }}
+            {{ form_errors(form.require) }}
+            {{ form_widget(form.require) }}
+        </p>
+        <p>
+            {{ form_label(form.description, "Description") }}
+            {{ form_errors(form.description) }}
+            {{ form_widget(form.description) }}
+        </p>
+        <p>
+            {{ form_label(form.tagsText, "Tags") }}
+            {{ form_errors(form.tagsText) }}
+            {{ form_widget(form.tagsText) }}
+        </p>
+        <p>
+            {{ form_label(form.homepage, "Homepage URL") }}
+            {{ form_errors(form.homepage) }}
+            {{ form_widget(form.homepage) }}
+        </p>
+        <p>
+            {{ form_label(form.license, "License (MIT, GPL, ..)") }}
+            {{ form_errors(form.license) }}
+            {{ form_widget(form.license) }}
+        </p>
+        {{ form_rest(form) }}
+        <input type="submit" value="Submit" />
+    </form>
+{% endblock %}

+ 79 - 0
src/Packagist/WebBundle/Resources/views/layout.html.twig

@@ -0,0 +1,79 @@
+<!DOCTYPE html>
+<!--[if lte IE 8 ]> <html lang="en" class="no-js oldie"> <![endif]-->
+<!--[if (gte IE 9)|!(IE)]><!--> <html lang="en" class="no-js"> <!--<![endif]-->
+    <head>
+        <meta charset="UTF-8" />
+
+        <title>{% block title %}Packagist{% endblock %}</title>
+        <meta name="description" content="" />
+        <meta name="author" content="Jordi Boggiano" />
+
+        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
+
+        <link rel="shortcut icon" href="{{ asset('favicon.ico') }}" />
+        <link rel="apple-touch-icon" href="{{ asset('apple-touch-icon.png') }}" />
+
+        <script type="text/javascript">
+            WebFontConfig = {
+                google: { families: [ 'Neuton', 'PT Sans' ] }
+            };
+            (function() {
+                var wf = document.createElement('script');
+                wf.src = '//ajax.googleapis.com/ajax/libs/webfont/1/webfont.js';
+                wf.type = 'text/javascript';
+                wf.async = 'true';
+                var s = document.getElementsByTagName('script')[0];
+                s.parentNode.insertBefore(wf, s);
+            })();
+        </script>
+        <link rel="stylesheet" href="{{ asset('bundles/packagistweb/css/main.css') }}" />
+
+        {# {% stylesheets '@PackagistWebBundle/Resources/public/css/main.css' filter="yui_css" output='css/main.css' %}
+            <link rel="stylesheet" href="{{ asset_url }}" />
+        {% endstylesheets %} #}
+
+        <script src="/js/libs/modernizr-1.7.min.js"></script>
+    </head>
+    <body>
+        <div class="container">
+            <div class="betawarn">WARNING - This is an experimental site, packages may come and go, this is not ready yet.</div>
+            <header>
+                {% if page is defined and page != 'submit' %}
+                <a class="submit" href="{{ path('submit') }}">Submit Package</a>
+                {% endif %}
+                <h1><a href="{{ path('home') }}">Packagist</a></h1>
+                <h2>The PHP package archivist.</h2>
+                {% if page is defined and page == 'home' %}
+                <p>Packagist is the main <a href="http://github.com/composer/composer">Composer</a> repository. It aggregates all sorts of PHP libraries that are installable with Composer. Browse packages below and if you have some open-source lib <a href="{{ path('submit') }}">submit it</a>!</p>
+                {% endif %}
+            </header>
+
+            {% if app.session.flash('success') %}
+                <div class="flash-message success">
+                    <p>{{ app.session.flash('success') }}</p>
+                </div>
+            {% endif %}
+            {% if app.session.flash('error') %}
+                <div class="flash-message error">
+                    <p>{{ app.session.flash('error') }}</p>
+                </div>
+            {% endif %}
+            {{ app.session.clearFlashes }}
+
+            <div class="main" role="main">
+                {% block content %}
+                {% endblock %}
+            </div>
+        </div>
+
+        <script src="//ajax.googleapis.com/ajax/libs/jquery/1.5.2/jquery.min.js"></script>
+        <script>!window.jQuery && document.write(unescape('%3Cscript src="/js/libs/jquery-1.5.2.min.js"%3E%3C/script%3E'))</script>
+        <script src="{{ asset('bundles/packagistweb/js/main.js') }}"></script>
+        {#<script>
+            var _gaq=[['_setAccount','UA-XXXXX-X'],['_trackPageview']]; // TODO Change UA-XXXXX-X
+            (function(d,t){var g=d.createElement(t),s=d.getElementsByTagName(t)[0];g.async=1;
+            g.src=('https:'==location.protocol?'//ssl':'//www')+'.google-analytics.com/ga.js';
+            s.parentNode.insertBefore(g,s)}(document,'script'));
+        </script>#}
+    </body>
+</html>

+ 265 - 0
web/.htaccess

@@ -0,0 +1,265 @@
+<IfModule mod_setenvif.c>
+  <IfModule mod_headers.c>
+    BrowserMatch MSIE ie
+    Header set X-UA-Compatible "IE=Edge,chrome=1" env=ie
+  </IfModule>
+</IfModule>
+
+<IfModule mod_headers.c>
+# Because X-UA-Compatible isn't sent to non-IE (to save header bytes),
+#   We need to inform proxies that content changes based on UA
+  Header append Vary User-Agent
+# Cache control is set only if mod_headers is enabled, so that's unncessary to declare
+</IfModule>
+
+# ----------------------------------------------------------------------
+# Webfont access
+# ----------------------------------------------------------------------
+
+# allow access from all domains for webfonts
+# alternatively you could only whitelist
+#   your subdomains like "sub.domain.com"
+
+<FilesMatch "\.(ttf|otf|eot|woff|font.css)$">
+  <IfModule mod_headers.c>
+    Header set Access-Control-Allow-Origin "*"
+  </IfModule>
+</FilesMatch>
+
+# ----------------------------------------------------------------------
+# Proper MIME type for all files
+# ----------------------------------------------------------------------
+
+# audio
+AddType audio/ogg                      oga ogg
+
+# video
+AddType video/ogg                      ogv
+AddType video/mp4                      mp4
+AddType video/webm                     webm
+
+# Proper svg serving. Required for svg webfonts on iPad
+#   twitter.com/FontSquirrel/status/14855840545
+AddType     image/svg+xml              svg svgz
+AddEncoding gzip                       svgz
+
+# webfonts
+AddType application/vnd.ms-fontobject  eot
+AddType font/truetype                  ttf
+AddType font/opentype                  otf
+AddType application/x-font-woff        woff
+
+# assorted types
+AddType image/x-icon                   ico
+AddType image/webp                     webp
+AddType text/cache-manifest            appcache manifest
+AddType text/x-component               htc
+AddType application/x-chrome-extension crx
+AddType application/x-xpinstall        xpi
+AddType application/octet-stream       safariextz
+
+# ----------------------------------------------------------------------
+# gzip compression
+# ----------------------------------------------------------------------
+
+<IfModule mod_deflate.c>
+
+# force deflate for mangled headers developer.yahoo.com/blogs/ydn/posts/2010/12/pushing-beyond-gzipping/
+<IfModule mod_setenvif.c>
+  <IfModule mod_headers.c>
+    SetEnvIfNoCase ^(Accept-EncodXng|X-cept-Encoding|X{15}|~{15}|-{15})$ ^((gzip|deflate)\s,?\s(gzip|deflate)?|X{4,13}|~{4,13}|-{4,13})$ HAVE_Accept-Encoding
+    RequestHeader append Accept-Encoding "gzip,deflate" env=HAVE_Accept-Encoding
+  </IfModule>
+</IfModule>
+# html, txt, css, js, json, xml, htc:
+<IfModule filter_module>
+  FilterDeclare   COMPRESS
+  FilterProvider  COMPRESS  DEFLATE resp=Content-Type /text/(html|css|javascript|plain|x(ml|-component))/
+  FilterProvider  COMPRESS  DEFLATE resp=Content-Type /application/(javascript|json|xml|x-javascript)/
+  FilterChain     COMPRESS
+  FilterProtocol  COMPRESS  change=yes;byteranges=no
+</IfModule>
+
+<IfModule !mod_filter.c>
+  # Legacy versions of Apache
+  AddOutputFilterByType DEFLATE text/html text/plain text/css application/json
+  AddOutputFilterByType DEFLATE text/javascript application/javascript application/x-javascript
+  AddOutputFilterByType DEFLATE text/xml application/xml text/x-component
+</IfModule>
+
+# webfonts and svg:
+  <FilesMatch "\.(ttf|otf|eot|svg)$" >
+    SetOutputFilter DEFLATE
+  </FilesMatch>
+</IfModule>
+
+##   # ----------------------------------------------------------------------
+##   # Expires headers (for better cache control)
+##   # ----------------------------------------------------------------------
+##
+##   # these are pretty far-future expires headers
+##   # they assume you control versioning with cachebusting query params like
+##   #   <script src="application.js?20100608">
+##   # additionally, consider that outdated proxies may miscache
+##   #   www.stevesouders.com/blog/2008/08/23/revving-filenames-dont-use-querystring/
+##
+##   # if you don't use filenames to version, lower the css and js to something like
+##   #   "access plus 1 week" or so
+##
+##   <IfModule mod_expires.c>
+##     ExpiresActive on
+##
+##   # Perhaps better to whitelist expires rules? Perhaps.
+##     ExpiresDefault                          "access plus 1 month"
+##
+##   # cache.appcache needs re-requests in FF 3.6 (thx Remy ~Introducing HTML5)
+##     ExpiresByType text/cache-manifest       "access plus 0 seconds"
+##
+##   # your document html
+##     ExpiresByType text/html                 "access plus 0 seconds"
+##
+##   # data
+##     ExpiresByType text/xml                  "access plus 0 seconds"
+##     ExpiresByType application/xml           "access plus 0 seconds"
+##     ExpiresByType application/json          "access plus 0 seconds"
+##
+##   # rss feed
+##     ExpiresByType application/rss+xml       "access plus 1 hour"
+##
+##   # favicon (cannot be renamed)
+##     ExpiresByType image/x-icon              "access plus 1 week"
+##
+##   # media: images, video, audio
+##     ExpiresByType image/gif                 "access plus 1 month"
+##     ExpiresByType image/png                 "access plus 1 month"
+##     ExpiresByType image/jpg                 "access plus 1 month"
+##     ExpiresByType image/jpeg                "access plus 1 month"
+##     ExpiresByType video/ogg                 "access plus 1 month"
+##     ExpiresByType audio/ogg                 "access plus 1 month"
+##     ExpiresByType video/mp4                 "access plus 1 month"
+##     ExpiresByType video/webm                "access plus 1 month"
+##
+##   # htc files  (css3pie)
+##     ExpiresByType text/x-component          "access plus 1 month"
+##
+##   # webfonts
+##     ExpiresByType font/truetype             "access plus 1 month"
+##     ExpiresByType font/opentype             "access plus 1 month"
+##     ExpiresByType application/x-font-woff   "access plus 1 month"
+##     ExpiresByType image/svg+xml             "access plus 1 month"
+##     ExpiresByType application/vnd.ms-fontobject "access plus 1 month"
+##
+##   # css and javascript
+##     ExpiresByType text/css                  "access plus 2 months"
+##     ExpiresByType application/javascript    "access plus 2 months"
+##     ExpiresByType text/javascript           "access plus 2 months"
+##
+##     <IfModule mod_headers.c>
+##       Header append Cache-Control "public"
+##     </IfModule>
+##
+##   </IfModule>
+
+
+
+# ----------------------------------------------------------------------
+# ETag removal
+# ----------------------------------------------------------------------
+
+# Since we're sending far-future expires, we don't need ETags for
+# static content.
+#   developer.yahoo.com/performance/rules.html#etags
+FileETag None
+
+# ----------------------------------------------------------------------
+# Start rewrite engine
+# ----------------------------------------------------------------------
+
+<IfModule mod_rewrite.c>
+    RewriteEngine On
+</IfModule>
+
+# ----------------------------------------------------------------------
+# Suppress or force the "www." at the beginning of URLs
+# ----------------------------------------------------------------------
+
+# The same content should never be available under two different URLs - especially not with and
+# without "www." at the beginning, since this can cause SEO problems (duplicate content).
+# That's why you should choose one of the alternatives and redirect the other one.
+
+# By default option 1 (no "www.") is activated. Remember: Shorter URLs are sexier.
+# no-www.org/faq.php?q=class_b
+
+# If you rather want to use option 2, just comment out all option 1 lines
+# and uncomment option 2.
+# IMPORTANT: NEVER USE BOTH RULES AT THE SAME TIME!
+
+# ----------------------------------------------------------------------
+
+# Option 1:
+# Rewrite "www.domain.com -> domain.com"
+
+<IfModule mod_rewrite.c>
+  RewriteCond %{HTTPS} !=on
+  RewriteCond %{HTTP_HOST} ^www\.(.+)$ [NC]
+  RewriteRule ^(.*)$ http://%1/$1 [R=301,L]
+</IfModule>
+
+# ----------------------------------------------------------------------
+# Symfony front controller
+# ----------------------------------------------------------------------
+
+<IfModule mod_rewrite.c>
+  RewriteCond %{REQUEST_FILENAME} !-f
+  RewriteRule ^(.*)$ app.php [QSA,L]
+</IfModule>
+
+# ----------------------------------------------------------------------
+# Built-in filename-based cache busting
+# ----------------------------------------------------------------------
+
+# If you're not using the build script to manage your filename version revving,
+# you might want to consider enabling this, which will route requests for
+# /css/all.20110203.css to /res/all.css
+
+# To understand why this is important and a better idea than all.css?v1231,
+# read: github.com/paulirish/html5-boilerplate/wiki/Version-Control-with-Cachebusting
+
+# Uncomment to enable.
+# <IfModule mod_rewrite.c>
+#   RewriteCond %{REQUEST_FILENAME} !-f
+#   RewriteCond %{REQUEST_FILENAME} !-d
+#   RewriteRule ^(.+)\.(\d+)\.(js|css|png|jpg|gif)$ $1.$3 [L]
+# </IfModule>
+
+# ----------------------------------------------------------------------
+# Prevent SSL cert warnings
+# ----------------------------------------------------------------------
+
+# Rewrite secure requests properly to prevent SSL cert warnings, e.g. prevent
+# https://www.domain.com when your cert only allows https://secure.domain.com
+# Uncomment the following lines to use this feature.
+
+# <IfModule mod_rewrite.c>
+#   RewriteCond %{SERVER_PORT} !^443
+#   RewriteRule (.*) https://example-domain-please-change-me.com/$1 [R=301,L]
+# </IfModule>
+
+# ----------------------------------------------------------------------
+# Prevent 404 errors for non-existing redirected folders
+# ----------------------------------------------------------------------
+
+# without -MultiViews, Apache will give a 404 for a rewrite if a folder of the same name does not exist
+#   e.g. /blog/hello : webmasterworld.com/apache/3808792.htm
+
+Options -MultiViews
+
+# ----------------------------------------------------------------------
+# UTF-8 encoding
+# ----------------------------------------------------------------------
+
+# use utf-8 encoding for anything served text/plain or text/html
+AddDefaultCharset utf-8
+
+# force utf-8 for a number of file formats
+AddCharset utf-8 .html .css .js .xml .json .rss

+ 15 - 0
web/app.php

@@ -0,0 +1,15 @@
+<?php
+
+$_SERVER['REQUEST_URI'] = str_replace('//get', '/get', $_SERVER['REQUEST_URI']);
+
+require_once __DIR__.'/../app/bootstrap.php.cache';
+require_once __DIR__.'/../app/AppKernel.php';
+//require_once __DIR__.'/../app/bootstrap_cache.php.cache';
+//require_once __DIR__.'/../app/AppCache.php';
+
+use Symfony\Component\HttpFoundation\Request;
+
+//$kernel = new AppCache(new AppKernel('prod', false));
+$kernel = new AppKernel('prod', false);
+$kernel->loadClassCache();
+$kernel->handle(Request::createFromGlobals())->send();

+ 20 - 0
web/app_dev.php

@@ -0,0 +1,20 @@
+<?php
+
+// this check prevents access to debug front controllers that are deployed by accident to production servers.
+// feel free to remove this, extend it, or make something more sophisticated.
+if (!in_array(@$_SERVER['REMOTE_ADDR'], array(
+    '127.0.0.1',
+    '::1',
+))) {
+    header('HTTP/1.0 403 Forbidden');
+    die('You are not allowed to access this file. Check '.basename(__FILE__).' for more information.');
+}
+
+require_once __DIR__.'/../app/bootstrap.php.cache';
+require_once __DIR__.'/../app/AppKernel.php';
+
+use Symfony\Component\HttpFoundation\Request;
+
+$kernel = new AppKernel('dev', true);
+$kernel->loadClassCache();
+$kernel->handle(Request::createFromGlobals())->send();

BIN
web/apple-touch-icon.png


BIN
web/favicon.ico


+ 4 - 0
web/robots.txt

@@ -0,0 +1,4 @@
+# www.robotstxt.org/
+# www.google.com/support/webmasters/bin/answer.py?hl=en&answer=156449
+
+User-agent: *