First git commit

This commit is contained in:
Simon Vieille 2015-03-02 20:07:17 +01:00
commit a830722f4c
100 changed files with 38622 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
vendor/

27
CONTRIBUTORS.md Normal file
View file

@ -0,0 +1,27 @@
Contributors
============
Development
-----------
* Simon Vieille (deepblue)
* Valérian Galliat (vava740)
Questions & Weightings
----------------------
Some questions are inspired by [Linux Distribution Chooser](http://www.zegeniestudios.net/ldc/),
a similar project that hasn't been updated since a while, and therefore
giving biased results (but the questions are still relevant).
* Simon Vieille (deepblue)
OS's Descriptions
-----------------
* Simon Vieille (deepblue)
Hosting
-------
* [Simon Vieille (deepblue)](https://linux-test.deblan.org/)

19
LICENSE Normal file
View file

@ -0,0 +1,19 @@
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
Version 2, December 2004
Copyright (C) 2004 Sam Hocevar <sam@hocevar.net>
Everyone is permitted to copy and distribute verbatim or modified
copies of this license document, and changing it is allowed as long
as the name is changed.
DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. You just DO WHAT THE FUCK YOU WANT TO.
This program is free software. It comes without any warranty, to
the extent permitted by applicable law. You can redistribute it
and/or modify it under the terms of the Do What The Fuck You Want
To Public License, Version 2, as published by Sam Hocevar. See
http://www.wtfpl.net/ for more details.

34
Makefile Normal file
View file

@ -0,0 +1,34 @@
COMPOSER ?= composer
PHPCS ?= vendor/bin/phpcs
PHPCS_FLAGS = --standard=PSR2
PHPCS_DIRS = app src
all: composer
prod: COMPOSER_INSTALL_FLAGS += --no-dev
prod: all optimize
composer:
@echo
#
# Installing application's dependencies.
#
$(COMPOSER) install $(COMPOSER_INSTALL_FLAGS)
optimize:
@echo
#
# Optimizing Composer's autoloader, can take some time.
#
$(COMPOSER) dump-autoload --optimize
update:
@echo
#
# Updating application's depencies.
#
$(COMPOSER) update
cs:
$(PHPCS) $(PHPCS_FLAGS) $(PHPCS_DIRS)

114
README.en.md Normal file
View file

@ -0,0 +1,114 @@
Linux Questionnaire
===================
This project provides a questionnaire that determines
the Linux distribution (or BSD system) which matches
your preferences.
Contact & Support
-----------------
### Project Management
* [Redmine](https://lab.deblan.org/client/projects/linux-questionnaire)
### Repository Browsers
* [Redmine](https://lab.deblan.org/client/projects/linux-questionnaire/repository)
* [WebSVN](https://guest:guest@svn.deblan.org/websvn/listing.php?repname=linux-questionnaire)
### IRC
* Server: `ssl.neutralnetwork.org`
* Channel: `#wiki`
* [Web client](https://ssl.neutralnetwork.org/irc/?channels=%23wiki)
### Forum
* [Official topic](http://www.jeuxvideo.com/forums/1-38-7795760-1-0-1-0-questionnaire-choix-distribution.htm)
Requirements
------------
* [PHP](https://www.php.net/) >= 5.4
* [Apache Subversion](https://subversion.apache.org/)
* [Composer](https://getcomposer.org/)
Installation
------------
svn co https://svn.deblan.org/svn/linux-questionnaire
cd linux-questionnaire
make
Composer
--------
Composer can maybe be downloaded from your system's repositories.
Else, follow the next instructions:
### Download
# With cURL
curl -sS https://getcomposer.org/installer | php
# With Wget
wget -O - -q https://getcomposer.org/installer | php
You can now use it with `php composer.phar [arguments]`.
### Executable
mv composer.phar composer
chmod +x composer
Use it with `./composer [arguments]`.
### Install
Assuming `~/bin` exists ans is in `$PATH`.
mv composer ~/bin
### Dependencies Installation (from `composer.lock`)
composer install
### Dependencies Update (will change `composer.lock`)
composer update
Makefile
--------
A Makefile is provided to automate some tasks.
* `make` will install application's dependencies via Composer,
* `make prod` will install dependencies without developmenent requirements
and run `make optimize`,
* `make optimize` will run Composer's autoloader dump script with classmap
only, without dynamic autoload rules,
* `make cs` will run PHP code sniffer with PSR-2 conventions.
Development Server
------------------
Use PHP's built-in development server, for example on `localhost`, port 8080:
cd web
php -S localhost:8080
Homepage Update
---------------
When the `README.*.md` files are updated, we also need to update
the matching views in `web/views/Questionnaire/readme.*.html.twig`. To do that,
a simple console script was created.
### Help
app/console generate:readme -h
### Basic Generation
app/console generate:readme

114
README.fr.md Normal file
View file

@ -0,0 +1,114 @@
Questionnaire Linux
===================
Ce projet consiste en un questionnaire permettant de déterminer
la distribution Linux (ou BSD) qui vous convient le plus.
Contact et support
------------------
### Gestion de projet
* [Redmine](https://lab.deblan.org/client/projects/linux-questionnaire)
### Arborescense du code
* [Redmine](https://lab.deblan.org/client/projects/linux-questionnaire/repository)
* [WebSVN](https://guest:guest@svn.deblan.org/websvn/listing.php?repname=linux-questionnaire)
### IRC
* Server: `ssl.neutralnetwork.org`
* Channel: `#wiki`
* [Web client](https://ssl.neutralnetwork.org/irc/?channels=%23wiki)
### Forum
* [Official topic](http://www.jeuxvideo.com/forums/1-38-7795760-1-0-1-0-questionnaire-choix-distribution.htm)
Prérequis
---------
* [PHP](https://www.php.net/) >= 5.4
* [Apache Subversion](https://subversion.apache.org/)
* [Composer](https://getcomposer.org/)
Installation
------------
svn co https://svn.deblan.org/svn/linux-questionnaire
cd linux-questionnaire
make
Composer
--------
Composer est peut-être téléchargeable depuis votre gestionnaire de paquets.
Sinon, suivez les instructions suivantes&nbsp;:
### Téléchargement
# Avec cURL
curl -sS https://getcomposer.org/installer | php
# Avec Wget
wget -O - -q https://getcomposer.org/installer | php
Vous pouvez maintenant l'utiliser avec `php composer.phar [arguments]`.
### Exécutable
mv composer.phar composer
chmod +x composer
S'utilise désormais avec `./composer [arguments]`.
### Installation
En partant du principe que le dossier `~/bin` existe et est dans le `$PATH`.
mv composer ~/bin
### Installation des dépendances (depuis `composer.lock`)
composer install
### Mise à jour des dépendances (modification de `composer.lock`)
composer update
Makefile
--------
Un Makefile est intégré pour automatiser certaines tâches.
* `make` installera les dépendances de l'application avec Composer,
* `make prod` installera les dépendances à l'exception de celles nécessaires
seulement au développement, et lancera `make optimize`,
* `make optimize` exécutera le script d'optimisation de Composer pour convertir
les chargements dynamiques de classes en *classmap*,
* `make cs` lancera un *code sniffer* PHP avec les conventions PSR-2.
Serveur de développement
------------------------
Vous pouvez utiliser le serveur de développement intégré à PHP, par exemple
sur `localhost`, port 8080&nbsp;:
cd web
php -S localhost:8080
Mise à jour de la page d'accueil
--------------------------------
Quand les `README.*.md` sont modifiés, il faut aussi mettre à jour les
vues correspondantes dans `web/views/Questionnaire/readme.*.html.twig`.
Pour ça, un script a été créé.
### Aide
app/console generate:readme -h
### Génération de base
app/console generate:readme

31
TODO.md Normal file
View file

@ -0,0 +1,31 @@
Todo
====
* Fix images in questionnaire config to not use an abslute path, using
a placeholder variable in the text (compatible with translation).
* Maybe find another way to include image than the plain HTML in config
file and translation.
* Show result value indicator.
* Take out some logic from controllers.
* Use service providers for most stuff in `app/bootstrap.php`.
* Peers:
* externalize YAML parsing in `QuestionnairePeer`,
* factorize peers to be injected as shared services.
* Maybe find a way to factorize site title and other variables,
for example using a Makefile to convert `.dist` files, like
`README.md.dist` with `%title%` replaced with real variable
during make process in target `README.md`.
* Not tested, but I guess the local server shown in readme
may not work, as it don't rewrite URL's with Apache config
override (`.htaccess`), and the website currently don't work
with `/index.php/*` links.
* Make the `.svnignore` work, or any way to ignore any directory
and its content.

36
app/bootstrap.php Normal file
View file

@ -0,0 +1,36 @@
<?php
require __DIR__.'/../vendor/autoload.php';
/**
* Wrap everything in a closure to preserve global scope and return the
* application.
*/
return call_user_func(function () {
$app = null;
/**
* This closure will be used to require other init files with a clean
* scope, with only access to `$app`.
*/
$closure = function () use (&$app) {
require func_get_arg(0);
};
$files = array();
foreach (new DirectoryIterator(__FILE__ . '.d') as $file) {
if (!$file->isDot() && $file->isFile()) {
$files[] = $file->getPathname();
}
}
// Sort init files, order is important
sort($files);
foreach ($files as $file) {
$closure($file);
}
return $app;
});

View file

@ -0,0 +1,7 @@
<?php
use Questionnaire\Application;
$app = Application::getInstance();
$app['root_path'] = __DIR__ . '/../..';

View file

@ -0,0 +1,9 @@
<?php
use Symfony\Component\Config\FileLocator;
$app['config.locator.path'] = $app['root_path'].'/app/config';
$app['config.locator'] = $app->share(function ($app) {
return new FileLocator($app['config.locator.path']);
});

View file

@ -0,0 +1,15 @@
<?php
use Knp\Provider\ConsoleServiceProvider;
use Questionnaire\Command\GenerateReadmeCommand;
$app->register(new ConsoleServiceProvider(), array(
'console.name' => 'Linux Questionnaire',
'console.version' => 'dev-master',
'console.project_directory' => $app['root_path'],
));
$app['console'] = $app->share($app->extend('console', function ($console) {
$console->add(new GenerateReadmeCommand());
return $console;
}));

View file

@ -0,0 +1,12 @@
<?php
$app->error(function (Exception $e, $code) use ($app) {
return $app['twig']->render(
'error.html.twig',
array(
'code' => $code,
'name' => get_class($e),
'exception' => $e,
)
);
});

View file

@ -0,0 +1,10 @@
<?php
use Silex\Provider\UrlGeneratorServiceProvider;
use Silex\Provider\SessionServiceProvider;
use Nicl\Silex\MarkdownServiceProvider;
$app->register(new UrlGeneratorServiceProvider());
$app->register(new SessionServiceProvider());
$app->register(new MarkdownServiceProvider());

View file

@ -0,0 +1,11 @@
<?php
use Questionnaire\Parser\ReadmeParser;
$app['dom_document'] = function ($app) {
return new DOMDocument();
};
$app['readme.parser'] = function ($app) {
return new ReadmeParser($app['dom_document']);
};

View file

@ -0,0 +1,14 @@
<?php
use Symfony\Component\Routing\Loader\YamlFileLoader;
$app['routing.file'] = 'routing.yml';
$app['routing.loader'] = $app->share(function ($app) {
return new YamlFileLoader($app['config.locator']);
});
$app['routes'] = $app->share($app->extend('routes', function ($routes, $app) {
$routes->addCollection($app['routing.loader']->load($app['routing.file']));
return $routes;
}));

View file

@ -0,0 +1,15 @@
<?php
use Silex\Provider\TwigServiceProvider;
$app->register(new TwigServiceProvider(), array(
'twig.path' => $app['root_path'].'/views',
'breadcrumb_separator' => ' - ',
));
$app->extend('twig', function ($twig, $app) {
$twig->addGlobal('web_path', $app['request']->getBaseUrl().'/');
$twig->addGlobal('breadcrumb_separator', $app['breadcrumb_separator']);
$twig->addGlobal('bs', $app['breadcrumb_separator']);
return $twig;
});

View file

@ -0,0 +1,58 @@
<?php
use Silex\Provider\TranslationServiceProvider;
use Symfony\Component\Translation\Loader\YamlFileLoader;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\AcceptHeader;
use Symfony\Component\HttpFoundation\RedirectResponse;
$app->register(new TranslationServiceProvider(), array(
'locale' => 'en',
'locale_fallback' => 'en',
'locales' => array('en', 'fr'), // Custom parameter, not Silex
));
$app['translator'] = $app->share($app->extend('translator', function ($translator, $app) {
$translator->addLoader('yaml', new YamlFileLoader());
foreach ($app['locales'] as $locale) {
$file = $app['root_path'].'/app/locales/'.$locale.'.yml';
if (is_file($file)) {
$translator->addResource('yaml', $file, $locale);
}
}
return $translator;
}));
$app['routes'] = $app->share($app->extend('routes', function ($routes, $app) {
$routes->addPrefix('/{_locale}');
$routes->addDefaults(array('_locale' => $app['locale_fallbacks'][0]));
$routes->addRequirements(array('_locale' => implode('|', $app['locales'])));
return $routes;
}));
/**
* Redirect home on right locale page, regarding of request accept locale or
* default fallback.
*/
$app->get('/', function (Request $request) use ($app) {
$accept = AcceptHeader::fromString($request->headers->get('Accept-Language'));
// Default locale fallback
$foundLocale = $app['translator']->getLocale();
foreach ($app['locales'] as $locale) {
if ($accept->has($locale)) {
$foundLocale = $locale;
break;
}
}
return new RedirectResponse($app['url_generator']->generate(
'homepage',
array('_locale' => $foundLocale)
));
});

View file

@ -0,0 +1,215 @@
questions:
10:
title: Pour vous, c'est quoi Linux ?
choices:
1: <img src="/assets/img/10-tty.png" alt="TTY" width="200">
2: <img src="/assets/img/10-lxde.png" alt="LXDE" width="200">
3: <img src="/assets/img/10-osx.png" alt="Mac OS X" width="200">
20:
title: Savez-vous ce qu'est une « distribution Linux » ?
# required: [[question_id, choice_id]]
choices:
1: yes
2: no
30:
title: Avez-vous déjà réussi à installer un système d'exploitation auparavant ?
choices:
1: yes
2: no
40:
title: Savez-vous comment partitionner un disque dur ?
choices:
1: yes
2: Non, je n'ai aucune idée de ce que « partitionnement » signifie
3: Non, mais je sais ce que « partitionnement » signifie
50:
title: Quel genre d'installateur préférez vous ?
choices:
1: Graphique (pointer et clicker)
2: En mode texte (clavier seulement)
3: Je n'y prête pas d'importance, du moment que c'est facile et que ça marche
60:
title: Comment évalueriez-vous votre niveau de qualification technique en informatique ?
choices:
1: Débutant
2: Intermediaire
3: Avancé
4: Expert
70:
title: Sur quel genre d'ordinateur allez-vous l'intaller ?
choices:
1: Un ordinateur portable (laptop)
2: Un ordinateur de bureau (desktop)
# 3: PS3
4: Cela peut-être les deux
80:
title: Quel est la fonction première de cet ordinateur ?
choices:
1: Desktop / système personnel
2: Station de travail
3: Serveur
90:
title: Avez-vous un processeur 64 bits ?
choices:
1: yes
2: no
100:
title: Quel âge à l'ordinateur sur lequel vous allez installer Linux ?
choices:
1: Il a plus que juste quelque années
2: Juste quelques années
3: Il est (presque) encore brillant
110:
title: Comment évalueriez-vous votre niveau de connaissance de Linux ?
choices:
1: Je n'ai jamais utilisé Linux auparavant / j'ai seulement essayé un petit peu
2: J'utilise Linux depuis un moment maintenant
3: Expérimenté, Je sais parfaitement où je vais
120:
title: Quel système de gestion de paquets préférez-vous ?
choices:
1: Je préfère le système DEB
2: Je préfère le système RPM
3: Je préfère compiler mes propres binaires
4: Je préfère un autre
5: Je ne sais pas / je n'y prête pas attention
130:
title: Avez-vous besoin que les paquets de développement soit installés ou aisément disponibles ?
choices:
1: Oui, aisément disponible
2: Oui, sur le CD
3: no
140:
title: Quel environement de bureau préférez-vous ?
choices:
1: Je préfère KDE
2: Je préfère GNOME
3: Je préfère LXDE
4: Je préfère avoir (aussi) d'autres environements de bureau disponibles
5: Je n'y prête pas attention
150:
title: Avez-vous besoin d'un accès facile à beaucoup de logiciels prêts à l'emploi ?
choices:
1: Oui, s'il vous plaît
2: Non merci / je n'y prête pas attention
160:
title: Veuillez sélectionner ce qui s'adapte le mieux à vous :
choices:
1: Je préfère utiliser des logiciels parfaitement stables, complètement testés
2: Je veux avoir les tout derniers logiciels mais néanmoins grandement stables
3: ça ne me gêne pas de tester des choses nouvelles, excitantes mais expérimentales
170:
title: Pourquoi êtes-vous ici ?
choices:
1: Études
2: Curiosité
3: Windows sucks
# 4: Cousin polonais
180:
title: Est-ce que la distribution Linux doit être gratuite ?
choices:
1: yes
2: no
190:
title: Voulez-vous inclure les Live CDs dans les résultats ?
choices:
1: yes
2: no
results:
10:
title: Debian
info: debian
weightings:
10: {1: 1}
15:
title: Debian (sid)
info: debian-sid
weightings:
10: {1: 1}
20:
title: Fedora
info: fedora
weightings:
10: {1: 1}
30:
title: openSUSE
info: opensuze
weightings:
10: {1: 1}
40:
title: Arch Linux
info: arch
weightings:
10: {1: 1}
50:
title: Slackware
info: slackware
weightings:
10: {1: 1}
60:
title: Frugalware
info: frugalware
weightings:
10: {1: 1}
70:
title: Mageia
info: mageia
weightings:
10: {1: 1}
80:
title: Sabayon
info: sabayon
weightings:
10: {1: 1}
90:
title: Toutou Linux
info: toutou-linux
weightings:
10: {1: 1}
100:
title: Puppy Linux
info: puppy-linux
weightings:
10: {1: 1}
110:
title: SliTaz
info: slitaz
weightings:
10: {1: 1}
120:
title: FreeBSD
info: freebsd
weightings:
10: {1: 1}
130:
title: PC-BSD
info: pcbsd
weightings:
10: {1: 1}
140:
title: OpenBSD
info: openbsd
weightings:
10: {1: 1}
150:
title: LFS
info: lfs
weightings:
10: {1: 1}
160:
title: DragonFly BSD
info: dragonfly
weightings:
10: {1: 1}
170:
title: Centos
info: centos
weightings:
10: {1: 1}
180:
title: Windows
info: windows
weightings:
10: {1: 1}

19
app/config/routing.yml Normal file
View file

@ -0,0 +1,19 @@
homepage:
path: /
defaults: { _controller: Questionnaire\Controller\QuestionnaireController::indexAction, _locale: en }
start:
path: /start
defaults: { _controller: Questionnaire\Controller\QuestionnaireController::startAction }
step:
path: /step/{step}
defaults: { _controller: Questionnaire\Controller\QuestionnaireController::stepAction }
result:
path: /result
defaults: { _controller: Questionnaire\Controller\QuestionnaireController::resultAction }
distros:
path: /distros
defaults: { _controller: Os\Controller\OsController::indexAction }

6
app/console Executable file
View file

@ -0,0 +1,6 @@
#!/usr/bin/env php
<?php
$app = require __DIR__.'/bootstrap.php';
$app['console']->run();

14
app/deploy Executable file
View file

@ -0,0 +1,14 @@
#!/bin/sh
cd "$(dirname "$0")/.."
deploy_preprod() {
rsync -avzoK --delete -e ssh * weblinuxtest@deblan.fr:/services/web/www/linux-test.deblan.org/project/
}
deploy_preprod_lan() {
rsync -avzoK --delete -e ssh * weblinuxtest@hinata:/services/web/www/linux-test.deblan.org/project/
}
([ -n "$1" ] && deploy_$1) || deploy_preprod

20
app/distros/en/arch.md Normal file
View file

@ -0,0 +1,20 @@
A First Level Header
====================
A Second Level Header
---------------------
Now is the time for all good men to come to
the aid of their country. This is just a
regular paragraph.
The quick brown fox jumped over the lazy
dog's back.
### Header 3
> This is a blockquote.
>
> This is the second paragraph in the blockquote.
>
> ## This is an H2 in a blockquote

0
app/distros/en/centos.md Normal file
View file

View file

@ -0,0 +1,20 @@
A First Level Header
====================
A Second Level Header
---------------------
Now is the time for all good men to come to
the aid of their country. This is just a
regular paragraph.
The quick brown fox jumped over the lazy
dog's back.
### Header 3
> This is a blockquote.
>
> This is the second paragraph in the blockquote.
>
> ## This is an H2 in a blockquote

20
app/distros/en/debian.md Normal file
View file

@ -0,0 +1,20 @@
A First Level Header
====================
A Second Level Header
---------------------
Now is the time for all good men to come to
the aid of their country. This is just a
regular paragraph.
The quick brown fox jumped over the lazy
dog's back.
### Header 3
> This is a blockquote.
>
> This is the second paragraph in the blockquote.
>
> ## This is an H2 in a blockquote

View file

20
app/distros/en/fedora.md Normal file
View file

@ -0,0 +1,20 @@
A First Level Header
====================
A Second Level Header
---------------------
Now is the time for all good men to come to
the aid of their country. This is just a
regular paragraph.
The quick brown fox jumped over the lazy
dog's back.
### Header 3
> This is a blockquote.
>
> This is the second paragraph in the blockquote.
>
> ## This is an H2 in a blockquote

View file

View file

@ -0,0 +1,20 @@
A First Level Header
====================
A Second Level Header
---------------------
Now is the time for all good men to come to
the aid of their country. This is just a
regular paragraph.
The quick brown fox jumped over the lazy
dog's back.
### Header 3
> This is a blockquote.
>
> This is the second paragraph in the blockquote.
>
> ## This is an H2 in a blockquote

0
app/distros/en/lfs.md Normal file
View file

20
app/distros/en/mageia.md Normal file
View file

@ -0,0 +1,20 @@
A First Level Header
====================
A Second Level Header
---------------------
Now is the time for all good men to come to
the aid of their country. This is just a
regular paragraph.
The quick brown fox jumped over the lazy
dog's back.
### Header 3
> This is a blockquote.
>
> This is the second paragraph in the blockquote.
>
> ## This is an H2 in a blockquote

View file

View file

@ -0,0 +1,20 @@
A First Level Header
====================
A Second Level Header
---------------------
Now is the time for all good men to come to
the aid of their country. This is just a
regular paragraph.
The quick brown fox jumped over the lazy
dog's back.
### Header 3
> This is a blockquote.
>
> This is the second paragraph in the blockquote.
>
> ## This is an H2 in a blockquote

0
app/distros/en/pcbsd.md Normal file
View file

View file

@ -0,0 +1,20 @@
A First Level Header
====================
A Second Level Header
---------------------
Now is the time for all good men to come to
the aid of their country. This is just a
regular paragraph.
The quick brown fox jumped over the lazy
dog's back.
### Header 3
> This is a blockquote.
>
> This is the second paragraph in the blockquote.
>
> ## This is an H2 in a blockquote

20
app/distros/en/sabayon.md Normal file
View file

@ -0,0 +1,20 @@
A First Level Header
====================
A Second Level Header
---------------------
Now is the time for all good men to come to
the aid of their country. This is just a
regular paragraph.
The quick brown fox jumped over the lazy
dog's back.
### Header 3
> This is a blockquote.
>
> This is the second paragraph in the blockquote.
>
> ## This is an H2 in a blockquote

View file

@ -0,0 +1,20 @@
A First Level Header
====================
A Second Level Header
---------------------
Now is the time for all good men to come to
the aid of their country. This is just a
regular paragraph.
The quick brown fox jumped over the lazy
dog's back.
### Header 3
> This is a blockquote.
>
> This is the second paragraph in the blockquote.
>
> ## This is an H2 in a blockquote

20
app/distros/en/slitaz.md Normal file
View file

@ -0,0 +1,20 @@
A First Level Header
====================
A Second Level Header
---------------------
Now is the time for all good men to come to
the aid of their country. This is just a
regular paragraph.
The quick brown fox jumped over the lazy
dog's back.
### Header 3
> This is a blockquote.
>
> This is the second paragraph in the blockquote.
>
> ## This is an H2 in a blockquote

View file

@ -0,0 +1,20 @@
A First Level Header
====================
A Second Level Header
---------------------
Now is the time for all good men to come to
the aid of their country. This is just a
regular paragraph.
The quick brown fox jumped over the lazy
dog's back.
### Header 3
> This is a blockquote.
>
> This is the second paragraph in the blockquote.
>
> ## This is an H2 in a blockquote

View file

20
app/distros/fr/arch.md Normal file
View file

@ -0,0 +1,20 @@
A First Level Header
====================
A Second Level Header
---------------------
Now is the time for all good men to come to
the aid of their country. This is just a
regular paragraph.
The quick brown fox jumped over the lazy
dog's back.
### Header 3
> This is a blockquote.
>
> This is the second paragraph in the blockquote.
>
> ## This is an H2 in a blockquote

0
app/distros/fr/centos.md Normal file
View file

View file

@ -0,0 +1,20 @@
A First Level Header
====================
A Second Level Header
---------------------
Now is the time for all good men to come to
the aid of their country. This is just a
regular paragraph.
The quick brown fox jumped over the lazy
dog's back.
### Header 3
> This is a blockquote.
>
> This is the second paragraph in the blockquote.
>
> ## This is an H2 in a blockquote

20
app/distros/fr/debian.md Normal file
View file

@ -0,0 +1,20 @@
A First Level Header
====================
A Second Level Header
---------------------
Now is the time for all good men to come to
the aid of their country. This is just a
regular paragraph.
The quick brown fox jumped over the lazy
dog's back.
### Header 3
> This is a blockquote.
>
> This is the second paragraph in the blockquote.
>
> ## This is an H2 in a blockquote

View file

20
app/distros/fr/fedora.md Normal file
View file

@ -0,0 +1,20 @@
A First Level Header
====================
A Second Level Header
---------------------
Now is the time for all good men to come to
the aid of their country. This is just a
regular paragraph.
The quick brown fox jumped over the lazy
dog's back.
### Header 3
> This is a blockquote.
>
> This is the second paragraph in the blockquote.
>
> ## This is an H2 in a blockquote

View file

View file

@ -0,0 +1,20 @@
A First Level Header
====================
A Second Level Header
---------------------
Now is the time for all good men to come to
the aid of their country. This is just a
regular paragraph.
The quick brown fox jumped over the lazy
dog's back.
### Header 3
> This is a blockquote.
>
> This is the second paragraph in the blockquote.
>
> ## This is an H2 in a blockquote

0
app/distros/fr/lfs.md Normal file
View file

20
app/distros/fr/mageia.md Normal file
View file

@ -0,0 +1,20 @@
A First Level Header
====================
A Second Level Header
---------------------
Now is the time for all good men to come to
the aid of their country. This is just a
regular paragraph.
The quick brown fox jumped over the lazy
dog's back.
### Header 3
> This is a blockquote.
>
> This is the second paragraph in the blockquote.
>
> ## This is an H2 in a blockquote

View file

View file

@ -0,0 +1,20 @@
A First Level Header
====================
A Second Level Header
---------------------
Now is the time for all good men to come to
the aid of their country. This is just a
regular paragraph.
The quick brown fox jumped over the lazy
dog's back.
### Header 3
> This is a blockquote.
>
> This is the second paragraph in the blockquote.
>
> ## This is an H2 in a blockquote

0
app/distros/fr/pcbsd.md Normal file
View file

View file

@ -0,0 +1,20 @@
A First Level Header
====================
A Second Level Header
---------------------
Now is the time for all good men to come to
the aid of their country. This is just a
regular paragraph.
The quick brown fox jumped over the lazy
dog's back.
### Header 3
> This is a blockquote.
>
> This is the second paragraph in the blockquote.
>
> ## This is an H2 in a blockquote

20
app/distros/fr/sabayon.md Normal file
View file

@ -0,0 +1,20 @@
A First Level Header
====================
A Second Level Header
---------------------
Now is the time for all good men to come to
the aid of their country. This is just a
regular paragraph.
The quick brown fox jumped over the lazy
dog's back.
### Header 3
> This is a blockquote.
>
> This is the second paragraph in the blockquote.
>
> ## This is an H2 in a blockquote

View file

@ -0,0 +1,20 @@
A First Level Header
====================
A Second Level Header
---------------------
Now is the time for all good men to come to
the aid of their country. This is just a
regular paragraph.
The quick brown fox jumped over the lazy
dog's back.
### Header 3
> This is a blockquote.
>
> This is the second paragraph in the blockquote.
>
> ## This is an H2 in a blockquote

20
app/distros/fr/slitaz.md Normal file
View file

@ -0,0 +1,20 @@
A First Level Header
====================
A Second Level Header
---------------------
Now is the time for all good men to come to
the aid of their country. This is just a
regular paragraph.
The quick brown fox jumped over the lazy
dog's back.
### Header 3
> This is a blockquote.
>
> This is the second paragraph in the blockquote.
>
> ## This is an H2 in a blockquote

View file

@ -0,0 +1,20 @@
A First Level Header
====================
A Second Level Header
---------------------
Now is the time for all good men to come to
the aid of their country. This is just a
regular paragraph.
The quick brown fox jumped over the lazy
dog's back.
### Header 3
> This is a blockquote.
>
> This is the second paragraph in the blockquote.
>
> ## This is an H2 in a blockquote

View file

25
app/locales/en.yml Normal file
View file

@ -0,0 +1,25 @@
base:
home: Home
distros: Distributions
wiki: Wiki
index:
start: Start now!
source: Browse source
result:
count: '{0} No result :(|{1} 1 result|]1,Inf] %count% results'
next: Next
previous: Previous
yes: Yes
no: No
#
# Native language names ("Français" will be displayed as-is in English),
# not needed to translate in other languages.
#
locale.native:
en: English
fr: Français

17
app/locales/fr.yml Normal file
View file

@ -0,0 +1,17 @@
base:
home: Accueil
distros: Distributions
wiki: Wiki
index:
start: Commencer maintenant !
source: Voir la source
result:
count: '{0} Aucun résultat :(|{1} 1 résultat|]1,Inf] %count% résultats'
next: Suivant
previous: Précédent
yes: Oui
no: Non

28
composer.json Normal file
View file

@ -0,0 +1,28 @@
{
"require": {
"silex/silex": "1.*",
"symfony/yaml": "2.*",
"symfony/config": "2.*",
"symfony/twig-bridge": "2.*",
"nicl/silex-markdown": "1.*",
"symfony/translation": "2.*",
"knplabs/console-service-provider": "dev-master",
"components/jquery": "1.*",
"twbs/bootstrap": "3.*"
},
"require-dev": {
"squizlabs/php_codesniffer": "1.*"
},
"config": {
"component-dir": "web/components"
},
"autoload": {
"psr-0": {
"": "src/"
}
}
}

View file

@ -0,0 +1,19 @@
<?php
namespace Os\Controller;
use Silex\Application;
use Questionnaire\Model\ResultPeer;
class OsController
{
public function indexAction(Application $app)
{
return $app['twig']->render(
'Os/index.html.twig',
array(
'results' => ResultPeer::retrieveResults(),
)
);
}
}

View file

@ -0,0 +1,22 @@
<?php
namespace Questionnaire;
use Silex\Application as SilexApplication;
/**
* @deprecated The static version should be avoided, use DI instead.
*/
class Application extends SilexApplication
{
public static function getInstance()
{
static $app;
if (null === $app) {
$app = new static;
}
return $app;
}
}

View file

@ -0,0 +1,229 @@
<?php
namespace Questionnaire\Command;
use Knp\Command\Command;
use Symfony\Component\Console\Input\InputArgument;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Output\StreamOutput;
use Questionnaire\Parser\ReadmeParser;
class GenerateReadmeCommand extends Command
{
/**
* @var mixed
*/
protected $in;
/**
* @var OutputInterface
*/
protected $out;
/**
* @var boolean
*/
protected $closeOut;
protected function configure()
{
$this
->setName('generate:readme')
->setDescription(
'Generate the HTML readme from Markdown with tags replacement.'
)
;
$this->addArgument(
'in',
InputArgument::OPTIONAL,
'Markdown file to process, <info>README.*.md</info> by default, stdin if <info>-</info>.'
);
$outDefault = '<info>web/views/Questionnaire/readme.*.html.twig</info>';
$this->addArgument(
'out',
InputArgument::OPTIONAL,
'Output HTML file, '.$outDefault.' by default, stdout if <info>-</info>.'
);
}
protected function execute(InputInterface $input, OutputInterface $output)
{
if (empty($input->getArgument('in')) && empty($input->getArgument('out'))) {
$this->handleDefault($input, $output);
return;
}
$this->before($input, $output);
$app = $this->getSilexApplication();
$markdownParser = $app['markdown'];
$readmeParser = $app['readme.parser'];
$md = stream_get_contents($this->in);
$html = $markdownParser->transformMarkdown($md);
$html = sprintf(
'
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
</head>
<body>
%s
</body>
</html>
',
$html
);
$readmeParser->load($html);
$readmeParser->stream($this->out);
$this->after();
}
protected function before(InputInterface $input, OutputInterface $output)
{
$in = $input->getArgument('in');
$out = $input->getArgument('out');
if (empty($in) || empty($out)) {
throw new \InvalidArgumentException('The `in` and `out` parameters must be provided together.');
}
if ($in === '-') {
$this->in = fopen('php://stdin', 'rb');
} else {
$this->validateFile($in, 'rb');
$this->in = fopen($in, 'rb');
}
if ($out === '-') {
$this->out = $output;
$this->closeOut = false;
} else {
try {
$this->validateFile($out, 'wb');
} catch (Exception $e) {
$this->clean();
throw $e;
}
$this->out = new StreamOutput(fopen($out, 'wb'));
$this->closeOut = true;
}
}
protected function after()
{
$this->clean();
}
protected function clean()
{
if (is_resource($this->in)) {
fclose($this->in);
}
if ($this->closeOut && is_resource($this->out->getStream())) {
fclose($this->out->getStream());
}
}
protected function handleDefault(InputInterface $input, OutputInterface $output)
{
$app = $this->getSilexApplication();
$dir = $this->getProjectDirectory();
$files = glob($dir.'/README.*.md');
if (empty($files)) {
throw new \InvalidArgumentException('No file was found matching `README.*.md`.');
}
foreach ($files as $file) {
/**
* Find the penultimate dot in string (assuming it ends with `\..{2}`).
*
* strrpos($input, '.', -4);
*
* Cut the filename from the penultimate dot plus 1 (don't include
* the dot in the final string) until the end of the string minus 3
* (don't take the last 3 characters).
*
* So, for `README.en.md`, this will extract `en`.
*
* For `README.en.markdown`, this will extract `markd` (stripping
* final `own`), because this will find the last dot instead of
* the penultimate dot.
*/
$lang = substr($file, strrpos($file, '.', -4) + 1, -3);
// Compote output file from input lang
$targets[] = $app['twig.path'].'/Questionnaire/readme.'.$lang.'.html.twig';
}
foreach ($files as $i => $file) {
$input->setArgument('in', $file);
$input->setArgument('out', $targets[$i]);
// Recursive execution for each file
$this->execute($input, $output);
}
}
/**
* @param string $file
* @param string $mode
* @see fopen()
* @throws \InvalidArgumentException
*/
protected function validateFile($file, $mode)
{
$read = 'r' === $mode[0];
$write = in_array($mode[0], array('w', 'a', 'c'), true);
$create = $write;
if ($mode[1] === '+') {
if ($read) {
$write = true;
} elseif ($write) {
$read = true;
}
}
if (!is_file($file)) {
if ($create) {
// Let the file be created
return;
}
throw new \InvalidArgumentException(sprintf(
'Given file "%s" does not exists.',
$file
));
}
if ($read && !is_readable($file)) {
throw new \InvalidArgumentException(sprintf(
'Given file "%s" is not readable.',
$file
));
}
if ($write && !is_writable($file)) {
throw new \InvalidArgumentException(sprintf(
'Given file "%s" is not writable.',
$file
));
}
}
}

View file

@ -0,0 +1,155 @@
<?php
namespace Questionnaire\Controller;
use Silex\Application;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Session\Session;
use Questionnaire\Model\Question;
use Questionnaire\Model\ResultPeer;
use Questionnaire\Model\QuestionPeer;
class QuestionnaireController
{
public function indexAction(Application $app)
{
return $app['twig']->render(
'Questionnaire/index.html.twig'
);
}
public function startAction(Application $app)
{
$this->initUserResponses($app['session']);
return $this->redirectToStep(1, $app);
}
public function stepAction($step, Application $app, Request $request)
{
$step = (int) $step;
$realStep = $step - 1;
$questions = QuestionPeer::retrieveQuestions();
if ($step > count($questions)) {
return $app->redirect($app['url_generator']->generate('result'));
}
$question = $questions[$realStep];
if ($request->isMethod('post')) {
$response = $request->request->get('response');
if ($response !== null) {
$response = (int) $response;
if (isset($question->getChoices()[$response])) {
$this->mergeUserResponse($question, $response, $app['session']);
return $this->redirectToStep($step + 1, $app);
}
}
}
if (!$question->isCompatibleWithResponses($app['session']->get('responses', array()))) {
return $this->redirectToStep($step + 1, $app);
}
return $app['twig']->render(
'Questionnaire/step.html.twig',
array(
'step' => $step,
'question' => $question,
'percent' => round((100 * $realStep) / count($questions)),
)
);
}
/**
* @todo Migrate calculation algorithm in its own class.
*/
public function resultAction(Application $app)
{
$responses = $app['session']->get('responses', array());
if (empty($responses)) {
return $this->redirectToStep(1, $app);
}
$results = ResultPeer::retrieveResults();
foreach ($results as $result) {
foreach ($responses as $questionId => $response) {
if (isset($result->getWeightings()[$questionId][$response])) {
$value = $result->getWeightings()[$questionId][$response];
if (false === $value) {
$result->isOut(true);
continue 2;
}
$result->addWeighting($value);
}
}
}
$finalResults = [];
foreach ($results as $result) {
if ($result->isOut()) {
continue;
}
$finalResults[] = $result;
}
$hasLoop = true;
$length = count($finalResults);
while ($hasLoop) {
$hasLoop = false;
for ($u = 0; $u < $length - 1; $u++) {
if ($finalResults[$u]->getValue() < $finalResults[$u + 1]->getValue()) {
$a = $finalResults[$u + 1];
$finalResults[$u + 1] = $finalResults[$u];
$finalResults[$u] = $a;
$hasLoop = true;
}
}
}
return $app['twig']->render(
'Questionnaire/result.html.twig',
array(
'results' => $finalResults,
)
);
}
protected function initUserResponses(Session $session)
{
$session->set('responses', array());
}
protected function mergeUserResponse(Question $question, $response, Session $session)
{
$responses = $session->get('responses', array());
$responses[$question->getId()] = $response;
$session->set('responses', $responses);
}
protected function redirectToStep($step, Application $app)
{
return $app->redirect($app['url_generator']->generate(
'step',
array(
'step' => $step
)
));
}
}

View file

@ -0,0 +1,116 @@
<?php
namespace Questionnaire\Model;
class Question
{
protected $id;
protected $title;
protected $choices = array();
protected $required = array();
public function hydrate(array $data)
{
foreach ($data as $k => $v) {
$setter = 'set'.ucfirst($k);
if (method_exists($this, $setter)) {
$this->$setter($v);
}
}
return $this;
}
public function setId($id)
{
$this->id = $id;
return $this;
}
public function getId()
{
return $this->id;
}
public function setTitle($title)
{
$this->title = $title;
return $this;
}
public function getTitle()
{
return $this->title;
}
public function setChoices(array $choices)
{
$this->choices = $choices;
return $this;
}
public function getChoices()
{
return $this->choices;
}
public function setRequired(array $required)
{
$this->required = $required;
return $this;
}
public function getRequired()
{
return $this->required;
}
public function getRequirements()
{
return $this->getRequired();
}
public function hasRequirements()
{
return count($this->getRequirements()) > 0;
}
public function isCompatibleWithResponses(array $response)
{
if (!$this->hasRequirements()) {
return true;
}
foreach ($response as $questionId => $response) {
$question = QuestionPeer::retrieveQuestionById($questionId);
if (null === $question) {
continue;
}
foreach ($this->getRequirements() as $requirement) {
list($requirementQuestionId, $requirementResponses) = $requirement;
if ($requirementQuestionId === $questionId) {
if (!is_array($requirementResponses)) {
$requirementResponses = array($requirementResponses);
}
if (!in_array($response, $requirementResponses)) {
return false;
}
}
}
}
return true;
}
}

View file

@ -0,0 +1,136 @@
<?php
namespace Questionnaire\Model;
use Questionnaire\Application;
use Symfony\Component\Yaml\Yaml;
class QuestionPeer extends QuestionnairePeer
{
protected static $questions = array();
public static function retrieveQuestions()
{
if (!empty(self::$questions)) {
return self::$questions;
}
$yaml = self::getYaml();
$questions = isset($yaml['questions']) ? $yaml['questions'] : array();
$collection = [];
foreach ($questions as $k => $v) {
if ($errors = self::validateYamlEnty($k, $v)) {
throw new \RuntimeException(
'Invalid entry for index "'.$k.'" is not valid:'.PHP_EOL.implode(PHP_EOL, $errors)
);
}
$question = new Question();
$collection[] = $question->hydrate(array_merge(array('id' => $k), $v));
}
if (empty($collection)) {
throw new \RuntimeException('At least one question must be defined.');
}
return self::$questions = $collection;
}
public static function retrieveQuestionById($id)
{
if (!is_int($id)) {
throw \InvalidArgumentException('You must provide a valid integer id.');
}
foreach (self::retrieveQuestions() as $d) {
if ($d->getId() === $id) {
return $d;
}
}
return null;
}
protected static function validateYamlEnty($key, $entry)
{
$errors = [];
if (!is_int($key)) {
$errors[] = 'The key must be a integer.';
}
if (!isset($entry['title'])) {
$errors[] = '"title" index must be defined (string).';
} else {
if (!is_string($entry['title'])) {
$errors[] = '"title" index must be defined as string.';
} else {
if (!trim($entry['title'])) {
$errors[] = '"title" index value can not be empty.';
}
}
}
if (empty($entry['choices'])) {
$errors[] = '"choices" index must be defined (array).';
} else {
if (!is_array($entry['choices'])) {
$errors[] = '"choices" index must be defined as array.';
} else {
if (empty($entry['choices'])) {
$errors[] = '"choices" index value can not be empty.';
} else {
foreach ($entry['choices'] as $k => $v) {
if (!is_int($k)) {
$errors[] = 'Choice with key "'.$k.'" must have an integer key.';
}
if (!is_string($v)) {
$errors[] = 'Choice value with key "'.$k.'" must be a string.';
} else {
if (empty($v)) {
$errors[] = 'Choice value with key "'.$k.'" cannot be empty.';
}
}
}
}
}
}
if (isset($entry['required'])) {
if (!is_array($entry['required'])) {
$errors[] = '"required" index must be defined as array.';
}
if (empty($entry['required'])) {
$errors[] = '"required" index value can not be empty if defined.';
} else {
foreach ($entry['required'] as $k => $v) {
if (!is_array($v)) {
$errors[] = 'At "required" index, value with key "'.$k.'" must be an array.';
} else {
if (count($v) !== 2) {
$errors[] = 'At "required" index, value with key "'.$k.'" must have 2 values: question index and choice index.';
} else {
if (!is_int($v[0])) {
$errors[] = 'At "required" index, value with key "'.$k.'" must have a integer for question\'s index.';
} else {
if ($v[0] >= $key) {
$errors[] = 'At "required" index, value with key "'.$k.'" must have a value smaller than "'.$key.'".';
}
}
if (!is_int($v[1]) && !is_array($v[1])) {
$errors[] = 'At "required" index, value with key "'.$k.'" must have a integer or array for choice\'s index.';
}
}
}
}
}
}
return !empty($errors) ? $errors : null;
}
}

View file

@ -0,0 +1,26 @@
<?php
namespace Questionnaire\Model;
use Questionnaire\Application;
use Symfony\Component\Yaml\Yaml;
abstract class QuestionnairePeer
{
/**
* @var string
*/
protected static $yaml;
protected static function getYaml()
{
if (null !== self::$yaml) {
return self::$yaml;
}
$file = Application::getInstance()['config.locator']
->locate('questionnaire.yml');
return self::$yaml = Yaml::parse($file);
}
}

View file

@ -0,0 +1,109 @@
<?php
namespace Questionnaire\Model;
class Result
{
protected $title;
protected $value = 0;
protected $isOut = false;
protected $info;
protected $weightings = array();
public function hydrate(array $data)
{
foreach ($data as $k => $v) {
$setter = 'set'.ucfirst($k);
if (method_exists($this, $setter)) {
$this->$setter($v);
}
}
return $this;
}
public function setTitle($title)
{
$this->title = $title;
return $this;
}
public function getTitle()
{
return $this->title;
}
public function setWeightings(array $weightings)
{
$this->weightings = $weightings;
return $this;
}
public function getWeightings()
{
return $this->weightings;
}
public function setValue($value)
{
$this->value = (int) $value;
return $this;
}
public function getValue()
{
return $this->value;
}
public function setIsOut($isOut)
{
$this->isOut = (bool) $isOut;
return $this;
}
public function getIsOut()
{
return $this->isOut;
}
public function isOut()
{
if (func_num_args() === 0) {
return $this->getIsOut();
}
return $this->setIsOut(func_get_arg(0));
}
public function addWeighting($weighting)
{
return $this->setValue($this->getValue() + $weighting);
}
public function setInfo($info)
{
$this->info = $info;
return $this;
}
public function getInfo($locale)
{
if (null === $this->info) {
return '';
}
$file = sprintf('../app/distros/%s/%s.md', $locale, $this->info);
return file_exists($file) ? file_get_contents($file) : '';
}
}

View file

@ -0,0 +1,108 @@
<?php
namespace Questionnaire\Model;
use Questionnaire\Configuration;
use Symfony\Component\Yaml\Yaml;
class ResultPeer extends QuestionnairePeer
{
protected static $results = array();
public static function retrieveResults()
{
if (!empty(self::$results)) {
return self::$results;
}
$yaml = self::getYaml();
$questions = isset($yaml['results']) ? $yaml['results'] : array();
$collection = [];
foreach ($questions as $k => $v) {
if ($errors = self::validateYamlEnty($k, $v)) {
throw new \RuntimeException(
'Invalid entry for index "'.$k.'" is not valid:'.PHP_EOL.implode(PHP_EOL, $errors)
);
}
$result = new Result();
$collection[] = $result->hydrate(array_merge(array('id' => $k), $v));
}
return self::$results = $collection;
}
public static function retrieveResultById($id)
{
if (!is_int($id)) {
throw \InvalidArgumentException('You must provide a valid integer id.');
}
foreach (self::retrieveResults() as $d) {
if ($d->getId() === $id) {
return $d;
}
}
return null;
}
protected static function validateYamlEnty($key, $entry)
{
$errors = [];
if (!is_int($key)) {
$errors[] = 'The key must be a integer.';
}
if (!isset($entry['title'])) {
$errors[] = '"title" index must be defined (string).';
} else {
if (!is_string($entry['title'])) {
$errors[] = '"title" index must be defined as string.';
} else {
if (!trim($entry['title'])) {
$errors[] = '"title" index value can not be empty.';
}
}
}
if (empty($entry['weightings'])) {
$errors[] = '"weightings" index must be defined (array).';
} else {
if (!is_array($entry['weightings'])) {
$errors[] = '"weightings" index must be defined as array.';
} else {
if (empty($entry['weightings'])) {
$errors[] = '"weightings" index value can not be empty.';
} else {
foreach ($entry['weightings'] as $k => $v) {
if (!is_int($k)) {
$errors[] = 'Weighting with key "'.$k.'" must have an integer key.';
}
if (!is_array($v)) {
$errors[] = 'Weighting value with key "'.$k.'" must be an array.';
} else {
foreach ($v as $q => $p) {
if (!is_bool($p) && !is_int($p)) {
$errors[] = 'Weighting value with key "'.$k.'" is invalid.';
}
}
}
}
}
}
}
// if (isset($entry['info'])) {
// if (!file_exists(sprintf('info/%s.md', $entry['info']))) {
// $errors[] = 'Info file does not exist.';
// }
// }
return !empty($errors) ? $errors : null;
}
}

View file

@ -0,0 +1,40 @@
<?php
namespace Questionnaire\Parser;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Output\StreamOutput;
abstract class Parser implements ParserInterface
{
/**
* @var OutputInterface
*/
protected $output;
public function stream(OutputInterface $output)
{
$this->output = $output;
$this->doStream();
$this->output = null;
}
public function capture()
{
$output = fopen('php://temp', 'w+b');
$this->stream(new StreamOutput($output));
rewind($output);
$data = stream_get_contents($output);
fclose($output);
return $data;
}
public function save($file)
{
$output = fopen($file, 'wb');
$this->stream(new StreamOutput($output));
fclose($file);
}
abstract protected function doStream();
}

View file

@ -0,0 +1,27 @@
<?php
namespace Questionnaire\Parser;
use Symfony\Component\Console\Output\OutputInterface;
interface ParserInterface
{
/**
* Parses data into given output.
*/
public function stream(OutputInterface $output);
/**
* Captures the stream output into a string buffer.
*
* @return string
*/
public function capture();
/**
* Writes the stream output into given file.
*
* @param string $file
*/
public function save($file);
}

View file

@ -0,0 +1,112 @@
<?php
namespace Questionnaire\Parser;
class ReadmeParser extends Parser
{
/**
* @var \DOMDocument
*/
protected $doc;
/**
* @var \DOMNodelist
*/
protected $nodes;
/**
* @var \DOMNode
*/
protected $node;
/**
* @var int
*/
protected $cur;
/**
* @param \DOMDocument $doc
*/
public function __construct(\DOMDocument $doc)
{
$this->doc = $doc;
}
/**
* @param string $html
* @throws \RuntimeException
*/
public function load($html)
{
$ok = $this->doc->loadHTML($html);
if (!$ok) {
throw new \RuntimeException('Unable to load given HTML content.');
}
$this->nodes = $this->doc->getElementsByTagName('body')->item(0)->childNodes;
$this->cur = 0;
}
public function doStream()
{
$output = $this->output;
$output->write('<div class="panel panel-default">');
$this->until('h1', 'say');
$output->write('<div class="panel-heading">');
$this->say();
$output->write('</div>');
$this->until('p', 'say');
$output->write('<div class="panel-body">');
$this->say();
$output->write('{% block start %}{% endblock %}');
$output->write('</div>');
$output->write('<ul class="list-group">');
$ok = $this->until('h2', 'say');
do {
$output->write('<li class="list-group-item">');
$this->say();
$ok = $this->until('h2', 'say');
$output->write('</li>');
} while ($ok);
$output->write('</ul>');
$output->writeln('</div>');
}
/**
* @param string $tag
* @param string $action
* @todo Find a way to avoid the copy/paste of `$this->node` line.
*/
protected function until($tag, $action = null)
{
$this->node = $this->nodes->item($this->cur++);
while ($tag !== $this->node->nodeName) {
if (null !== $action) {
$this->$action();
}
$this->node = $this->nodes->item($this->cur++);
}
return null !== $this->node;
}
protected function say()
{
$this->output->write($this->doc->saveHTML($this->node));
}
protected function sayText()
{
$this->output->write($this->node->textContent);
}
}

35
views/Os/index.html.twig Normal file
View file

@ -0,0 +1,35 @@
{% extends 'base.html.twig' %}
{% block content %}
<div class="row">
<div class="col-md-2">
<nav>
<ul class="list-group">
{% for result in results %}
<li class="list-group-item"><a href="#{{ result.title }}">{{ result.title }}</a></li>
{% endfor %}
</ul>
</nav>
</div>
<div class="col-md-10">
<section>
{% if results|length == 0 %}
<h1>No result :(</h1>
{% else %}
{% for result in results %}
<article id="{{ result.title }}">
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">{{ result.title }}</h2>
</div>
<div class="panel-body">
{{ result.info(app.request.locale)|markdown }}
</div>
</div>
</article>
{% endfor %}
{% endif %}
</section>
</div>
</div>
{% endblock %}

View file

@ -0,0 +1,13 @@
{% extends 'base.html.twig' %}
{% block content %}
<h1>
{% block heading %}
{{ block('main_title') }}
{% endblock %}
</h1>
{% if block('main_title')|length %}<hr>{% endif %}
{% block questionnaire_content %}{% endblock %}
{% endblock %}

View file

@ -0,0 +1,19 @@
{% extends 'base.html.twig' %}
{% block content %}
{% embed 'Questionnaire/readme.' ~ app.request.locale ~ '.html.twig' %}
{% block start %}
<p>
<a class="btn btn-success" href="{{ path('start') }}">
{{ 'index.start'|trans }}
</a>
<a
class="btn btn-info"
href="https://guest:guest@svn.deblan.org/websvn/listing.php?repname=linux-questionnaire"
>
{{ 'index.source'|trans }}
</a>
</p>
{% endblock %}
{% endembed %}
{% endblock %}

View file

@ -0,0 +1,131 @@
<div class="panel panel-default">
<div class="panel-heading"><h1>Linux Questionnaire</h1></div>
<div class="panel-body"><p>This project provides a questionnaire that determines
the Linux distribution (or BSD system) which matches
your preferences.</p>{% block start %}{% endblock %}</div><ul class="list-group">
<li class="list-group-item"><h2>Contact &amp; Support</h2>
<h3>Project Management</h3>
<ul>
<li><a href="https://lab.deblan.org/client/projects/linux-questionnaire">Redmine</a></li>
</ul>
<h3>Repository Browsers</h3>
<ul>
<li><a href="https://lab.deblan.org/client/projects/linux-questionnaire/repository">Redmine</a></li>
<li><a href="https://guest:guest@svn.deblan.org/websvn/listing.php?repname=linux-questionnaire">WebSVN</a></li>
</ul>
<h3>IRC</h3>
<ul>
<li>Server: <code>ssl.neutralnetwork.org</code>
</li>
<li>Channel: <code>#wiki</code>
</li>
<li><a href="https://ssl.neutralnetwork.org/irc/?channels=%23wiki">Web client</a></li>
</ul>
<h3>Forum</h3>
<ul>
<li><a href="http://www.jeuxvideo.com/forums/1-38-7795760-1-0-1-0-questionnaire-choix-distribution.htm">Official topic</a></li>
</ul>
</li><li class="list-group-item"><h2>Requirements</h2>
<ul>
<li>
<a href="https://www.php.net/">PHP</a> &gt;= 5.4</li>
<li><a href="https://subversion.apache.org/">Apache Subversion</a></li>
<li><a href="https://getcomposer.org/">Composer</a></li>
</ul>
</li><li class="list-group-item"><h2>Installation</h2>
<pre><code>svn co https://svn.deblan.org/svn/linux-questionnaire
cd linux-questionnaire
make
</code></pre>
</li><li class="list-group-item"><h2>Composer</h2>
<p>Composer can maybe be downloaded from your system's repositories.
Else, follow the next instructions:</p>
<h3>Download</h3>
<pre><code># With cURL
curl -sS https://getcomposer.org/installer | php
# With Wget
wget -O - -q https://getcomposer.org/installer | php
</code></pre>
<p>You can now use it with <code>php composer.phar [arguments]</code>.</p>
<h3>Executable</h3>
<pre><code>mv composer.phar composer
chmod +x composer
</code></pre>
<p>Use it with <code>./composer [arguments]</code>.</p>
<h3>Install</h3>
<p>Assuming <code>~/bin</code> exists ans is in <code>$PATH</code>.</p>
<pre><code>mv composer ~/bin
</code></pre>
<h3>Dependencies Installation (from <code>composer.lock</code>)</h3>
<pre><code>composer install
</code></pre>
<h3>Dependencies Update (will change <code>composer.lock</code>)</h3>
<pre><code>composer update
</code></pre>
</li><li class="list-group-item"><h2>Makefile</h2>
<p>A Makefile is provided to automate some tasks.</p>
<ul>
<li>
<code>make</code> will install application's dependencies via Composer,</li>
<li>
<code>make prod</code> will install dependencies without developmenent requirements
and run <code>make optimize</code>,</li>
<li>
<code>make optimize</code> will run Composer's autoloader dump script with classmap
only, without dynamic autoload rules,</li>
<li>
<code>make cs</code> will run PHP code sniffer with PSR-2 conventions.</li>
</ul>
</li><li class="list-group-item"><h2>Development Server</h2>
<p>Use PHP's built-in development server, for example on <code>localhost</code>, port 8080:</p>
<pre><code>cd web
php -S localhost:8080
</code></pre>
</li><li class="list-group-item"><h2>Homepage Update</h2>
<p>When the <code>README.*.md</code> files are updated, we also need to update
the matching views in <code>web/views/Questionnaire/readme.*.html.twig</code>. To do that,
a simple console script was created.</p>
<h3>Help</h3>
<pre><code>app/console generate:readme -h
</code></pre>
<h3>Basic Generation</h3>
<pre><code>app/console generate:readme
</code></pre>
</li></ul></div>

View file

@ -0,0 +1,131 @@
<div class="panel panel-default">
<div class="panel-heading"><h1>Questionnaire Linux</h1></div>
<div class="panel-body"><p>Ce projet consiste en un questionnaire permettant de déterminer
la distribution Linux (ou BSD) qui vous convient le plus.</p>{% block start %}{% endblock %}</div><ul class="list-group">
<li class="list-group-item"><h2>Contact et support</h2>
<h3>Gestion de projet</h3>
<ul>
<li><a href="https://lab.deblan.org/client/projects/linux-questionnaire">Redmine</a></li>
</ul>
<h3>Arborescense du code</h3>
<ul>
<li><a href="https://lab.deblan.org/client/projects/linux-questionnaire/repository">Redmine</a></li>
<li><a href="https://guest:guest@svn.deblan.org/websvn/listing.php?repname=linux-questionnaire">WebSVN</a></li>
</ul>
<h3>IRC</h3>
<ul>
<li>Server: <code>ssl.neutralnetwork.org</code>
</li>
<li>Channel: <code>#wiki</code>
</li>
<li><a href="https://ssl.neutralnetwork.org/irc/?channels=%23wiki">Web client</a></li>
</ul>
<h3>Forum</h3>
<ul>
<li><a href="http://www.jeuxvideo.com/forums/1-38-7795760-1-0-1-0-questionnaire-choix-distribution.htm">Official topic</a></li>
</ul>
</li><li class="list-group-item"><h2>Prérequis</h2>
<ul>
<li>
<a href="https://www.php.net/">PHP</a> &gt;= 5.4</li>
<li><a href="https://subversion.apache.org/">Apache Subversion</a></li>
<li><a href="https://getcomposer.org/">Composer</a></li>
</ul>
</li><li class="list-group-item"><h2>Installation</h2>
<pre><code>svn co https://svn.deblan.org/svn/linux-questionnaire
cd linux-questionnaire
make
</code></pre>
</li><li class="list-group-item"><h2>Composer</h2>
<p>Composer est peut-être téléchargeable depuis votre gestionnaire de paquets.
Sinon, suivez les instructions suivantes :</p>
<h3>Téléchargement</h3>
<pre><code># Avec cURL
curl -sS https://getcomposer.org/installer | php
# Avec Wget
wget -O - -q https://getcomposer.org/installer | php
</code></pre>
<p>Vous pouvez maintenant l'utiliser avec <code>php composer.phar [arguments]</code>.</p>
<h3>Exécutable</h3>
<pre><code>mv composer.phar composer
chmod +x composer
</code></pre>
<p>S'utilise désormais avec <code>./composer [arguments]</code>.</p>
<h3>Installation</h3>
<p>En partant du principe que le dossier <code>~/bin</code> existe et est dans le <code>$PATH</code>.</p>
<pre><code>mv composer ~/bin
</code></pre>
<h3>Installation des dépendances (depuis <code>composer.lock</code>)</h3>
<pre><code>composer install
</code></pre>
<h3>Mise à jour des dépendances (modification de <code>composer.lock</code>)</h3>
<pre><code>composer update
</code></pre>
</li><li class="list-group-item"><h2>Makefile</h2>
<p>Un Makefile est intégré pour automatiser certaines tâches.</p>
<ul>
<li>
<code>make</code> installera les dépendances de l'application avec Composer,</li>
<li>
<code>make prod</code> installera les dépendances à l'exception de celles nécessaires
seulement au développement, et lancera <code>make optimize</code>,</li>
<li>
<code>make optimize</code> exécutera le script d'optimisation de Composer pour convertir
les chargements dynamiques de classes en <em>classmap</em>,</li>
<li>
<code>make cs</code> lancera un <em>code sniffer</em> PHP avec les conventions PSR-2.</li>
</ul>
</li><li class="list-group-item"><h2>Serveur de développement</h2>
<p>Vous pouvez utiliser le serveur de développement intégré à PHP, par exemple
sur <code>localhost</code>, port 8080 :</p>
<pre><code>cd web
php -S localhost:8080
</code></pre>
</li><li class="list-group-item"><h2>Mise à jour de la page d'accueil</h2>
<p>Quand les <code>README.*.md</code> sont modifiés, il faut aussi mettre à jour les
vues correspondantes dans <code>web/views/Questionnaire/readme.*.html.twig</code>.
Pour ça, un script a été créé.</p>
<h3>Aide</h3>
<pre><code>app/console generate:readme -h
</code></pre>
<h3>Génération de base</h3>
<pre><code>app/console generate:readme
</code></pre>
</li></ul></div>

View file

@ -0,0 +1,37 @@
{% extends 'base.html.twig' %}
{% block content %}
<div class="row">
<div class="col-md-2">
<nav>
<ul class="list-group">
{% for result in results %}
<li class="list-group-item"><a href="#{{ result.title }}">{{ result.title }}</a></li>
{% endfor %}
</ul>
</nav>
</div>
<div class="col-md-10">
<section>
<h1>
{% set count = results|length %}
{% transchoice count with {'%count%': count} %}
result.count
{% endtranschoice %}
</h1>
{% for result in results %}
<article id="{{ result.title }}">
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">{{ result.title }}</h2>
</div>
<div class="panel-body">
{{ result.info(app.request.locale)|markdown }}
</div>
</div>
</article>
{% endfor %}
</section>
</div>
</div>
{% endblock %}

View file

@ -0,0 +1,61 @@
{% extends 'Questionnaire/base.html.twig' %}
{% block title %}
{{ question.title }}
{{ bs }}
{{ parent() }}
{% endblock %}
{% block questionnaire_content %}
<div class="text-right">
{{ percent }}%
</div>
<div class="progress progress-striped">
<div
class="progress-bar progress-bar-success"
role="progressbar"
aria-valuenow="{{ percent }}"
aria-valuemin="0"
aria-valuemax="100"
style="width: {{ percent }}%"
>
<span class="sr-only">{{ percent }}% Complete</span>
</div>
</div>
<form action="{{ path('step', {step: step}) }}" method="post">
<article>
<div class="panel panel-default">
<div class="panel-heading">
<h2 class="panel-title">{{ question.title|trans }}</h2>
</div>
<div class="panel-body">
{% for key, choice in question.choices %}
<p>
<input
type="radio"
name="response"
{% if loop.first %}
checked
{% endif %}
value="{{ key }}"
id="response_{{ key }}"
>
<label for="response_{{ key }}">
{{ choice|trans|raw }}
</label>
</p>
{% endfor %}
</div>
</div>
</article>
<p>
{% if step > 1 %}
<a class="btn btn-success" href="{{ path('step', {step: step - 1}) }}">{{ 'previous'|trans }}</a>
{% endif %}
<button class="btn btn-success" type="submit">{{ 'next'|trans }}</button>
</p>
</form>
{% endblock %}

76
views/base.html.twig Normal file
View file

@ -0,0 +1,76 @@
{% extends 'layout.html.twig' %}
{% block main_title %}Linux Questionnaire{% endblock %}
{% block title %}{{ block('main_title') }}{% endblock %}
{% block stylesheets %}
{% set assets = assets|default([])|merge([
web_path ~ 'components/bootstrap/css/bootstrap.min.css',
web_path ~ 'components/bootstrap/css/bootstrap-theme.min.css',
web_path ~ 'assets/css/main.css',
]) %}
{{ parent() }}
{% endblock %}
{% block body %}
{% import _self as macros %}
{% macro link(url, label) %}
<li
{% if app.request.getPathInfo == url %}
class="active"
{% endif %}
>
<a href="{{ url }}">{{ label }}</a>
</li>
{% endmacro %}
<nav class="navbar navbar-inverse navbar-top" role="navigation">
<div class="container">
<div class="navbar-hader">
<ul class="nav navbar-nav">
{{ macros.link(path('homepage'), 'base.home'|trans) }}
{{ macros.link(path('distros'), 'base.distros'|trans) }}
{{ macros.link('http://francoisautin.free.fr/wikilinux/pmwiki.php', 'base.wiki'|trans) }}
</ul>
</div>
<div class="navbar-collapse collapse">
<form role="form" class="navbar-form navbar-right">
<div class="btn-group">
<button type="button" class="btn btn-default dropdown-toggle" data-toggle="dropdown">
{{ ('locale.native.' ~ app.locale)|trans }} <span class="caret"></span>
</button>
<ul class="dropdown-menu" role="menu">
{% for locale in app.locales %}
{% if locale != app.locale %}
<li>
<a
href="{{ path(app.request.attributes.get('_route'), app.request.attributes.get('_route_params')|merge({_locale: locale})) }}"
>
{{ ('locale.native.' ~ locale)|trans }}
</a>
</li>
{% endif %}
{% endfor %}
</ul>
</div>
</form>
</div>
</div>
</nav>
<div class="container">
{% block content %}{% endblock %}
</div>
{% endblock %}
{% block javascripts %}
{% set assets = assets|default([])|merge([
web_path ~ 'components/jquery/jquery.min.js',
web_path ~ 'components/bootstrap/js/bootstrap.min.js',
]) %}
{{ parent() }}
{% endblock %}

15
views/error.html.twig Normal file
View file

@ -0,0 +1,15 @@
{% extends 'layout.html.twig' %}
{% block title %}
Error {{ code }} - {{ name }}
{% endblock %}
{% block body %}
<h1>{{ name }}</h1>
<p><em>In file "{{ exception.file }}" at line {{ exception.line }}</em></p>
<p>{{ exception.message }}</p>
<h2>Stacktrace</h2>
<pre>{{ exception.traceAsString }}</pre>
{% endblock %}

20
views/layout.html.twig Normal file
View file

@ -0,0 +1,20 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<title>{% block title %}{% endblock %}</title>
{% block stylesheets %}
{% for asset in assets %}
<link href="{{ asset }}" rel="stylesheet">
{% endfor %}
{% endblock %}
</head>
<body>
{% block body %}{% endblock %}
{% block javascripts %}
{% for asset in assets %}
<script src="{{ asset }}"></script>
{% endfor %}
{% endblock %}
</body>
</html>

14
web/.htaccess Normal file
View file

@ -0,0 +1,14 @@
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteCond %{REQUEST_URI}::$1 ^(/.+)/(.*)::\2$
RewriteRule ^(.*) - [E=BASE:%1]
RewriteCond %{ENV:REDIRECT_STATUS} ^$
RewriteRule ^index\.php(/(.*)|$) %{ENV:BASE}/$2 [R=301,L]
RewriteCond %{REQUEST_FILENAME} -f
RewriteRule .? - [L]
RewriteRule .? %{ENV:BASE}/index.php [L]
</IfModule>

13
web/assets/css/main.css Normal file
View file

@ -0,0 +1,13 @@
.navbar-top {
border-radius: 0;
border-width: 0 0 1px;
}
.panel h2 {
font-size: 18px;
}
.panel h3 {
font-size: 12px;
font-weight: 700;
}

BIN
web/assets/img/10-lxde.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

BIN
web/assets/img/10-osx.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 58 KiB

BIN
web/assets/img/10-tty.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

1
web/components/bootstrap Symbolic link
View file

@ -0,0 +1 @@
../../vendor/twbs/bootstrap/dist

10337
web/components/jquery/jquery-built.js vendored Normal file

File diff suppressed because it is too large Load diff

511
web/components/jquery/jquery-migrate.js vendored Normal file
View file

@ -0,0 +1,511 @@
/*!
* jQuery Migrate - v1.1.1 - 2013-02-16
* https://github.com/jquery/jquery-migrate
* Copyright 2005, 2013 jQuery Foundation, Inc. and other contributors; Licensed MIT
*/
(function( jQuery, window, undefined ) {
// See http://bugs.jquery.com/ticket/13335
// "use strict";
var warnedAbout = {};
// List of warnings already given; public read only
jQuery.migrateWarnings = [];
// Set to true to prevent console output; migrateWarnings still maintained
// jQuery.migrateMute = false;
// Show a message on the console so devs know we're active
if ( !jQuery.migrateMute && window.console && console.log ) {
console.log("JQMIGRATE: Logging is active");
}
// Set to false to disable traces that appear with warnings
if ( jQuery.migrateTrace === undefined ) {
jQuery.migrateTrace = true;
}
// Forget any warnings we've already given; public
jQuery.migrateReset = function() {
warnedAbout = {};
jQuery.migrateWarnings.length = 0;
};
function migrateWarn( msg) {
if ( !warnedAbout[ msg ] ) {
warnedAbout[ msg ] = true;
jQuery.migrateWarnings.push( msg );
if ( window.console && console.warn && !jQuery.migrateMute ) {
console.warn( "JQMIGRATE: " + msg );
if ( jQuery.migrateTrace && console.trace ) {
console.trace();
}
}
}
}
function migrateWarnProp( obj, prop, value, msg ) {
if ( Object.defineProperty ) {
// On ES5 browsers (non-oldIE), warn if the code tries to get prop;
// allow property to be overwritten in case some other plugin wants it
try {
Object.defineProperty( obj, prop, {
configurable: true,
enumerable: true,
get: function() {
migrateWarn( msg );
return value;
},
set: function( newValue ) {
migrateWarn( msg );
value = newValue;
}
});
return;
} catch( err ) {
// IE8 is a dope about Object.defineProperty, can't warn there
}
}
// Non-ES5 (or broken) browser; just set the property
jQuery._definePropertyBroken = true;
obj[ prop ] = value;
}
if ( document.compatMode === "BackCompat" ) {
// jQuery has never supported or tested Quirks Mode
migrateWarn( "jQuery is not compatible with Quirks Mode" );
}
var attrFn = jQuery( "<input/>", { size: 1 } ).attr("size") && jQuery.attrFn,
oldAttr = jQuery.attr,
valueAttrGet = jQuery.attrHooks.value && jQuery.attrHooks.value.get ||
function() { return null; },
valueAttrSet = jQuery.attrHooks.value && jQuery.attrHooks.value.set ||
function() { return undefined; },
rnoType = /^(?:input|button)$/i,
rnoAttrNodeType = /^[238]$/,
rboolean = /^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i,
ruseDefault = /^(?:checked|selected)$/i;
// jQuery.attrFn
migrateWarnProp( jQuery, "attrFn", attrFn || {}, "jQuery.attrFn is deprecated" );
jQuery.attr = function( elem, name, value, pass ) {
var lowerName = name.toLowerCase(),
nType = elem && elem.nodeType;
if ( pass ) {
// Since pass is used internally, we only warn for new jQuery
// versions where there isn't a pass arg in the formal params
if ( oldAttr.length < 4 ) {
migrateWarn("jQuery.fn.attr( props, pass ) is deprecated");
}
if ( elem && !rnoAttrNodeType.test( nType ) &&
(attrFn ? name in attrFn : jQuery.isFunction(jQuery.fn[name])) ) {
return jQuery( elem )[ name ]( value );
}
}
// Warn if user tries to set `type`, since it breaks on IE 6/7/8; by checking
// for disconnected elements we don't warn on $( "<button>", { type: "button" } ).
if ( name === "type" && value !== undefined && rnoType.test( elem.nodeName ) && elem.parentNode ) {
migrateWarn("Can't change the 'type' of an input or button in IE 6/7/8");
}
// Restore boolHook for boolean property/attribute synchronization
if ( !jQuery.attrHooks[ lowerName ] && rboolean.test( lowerName ) ) {
jQuery.attrHooks[ lowerName ] = {
get: function( elem, name ) {
// Align boolean attributes with corresponding properties
// Fall back to attribute presence where some booleans are not supported
var attrNode,
property = jQuery.prop( elem, name );
return property === true || typeof property !== "boolean" &&
( attrNode = elem.getAttributeNode(name) ) && attrNode.nodeValue !== false ?
name.toLowerCase() :
undefined;
},
set: function( elem, value, name ) {
var propName;
if ( value === false ) {
// Remove boolean attributes when set to false
jQuery.removeAttr( elem, name );
} else {
// value is true since we know at this point it's type boolean and not false
// Set boolean attributes to the same name and set the DOM property
propName = jQuery.propFix[ name ] || name;
if ( propName in elem ) {
// Only set the IDL specifically if it already exists on the element
elem[ propName ] = true;
}
elem.setAttribute( name, name.toLowerCase() );
}
return name;
}
};
// Warn only for attributes that can remain distinct from their properties post-1.9
if ( ruseDefault.test( lowerName ) ) {
migrateWarn( "jQuery.fn.attr('" + lowerName + "') may use property instead of attribute" );
}
}
return oldAttr.call( jQuery, elem, name, value );
};
// attrHooks: value
jQuery.attrHooks.value = {
get: function( elem, name ) {
var nodeName = ( elem.nodeName || "" ).toLowerCase();
if ( nodeName === "button" ) {
return valueAttrGet.apply( this, arguments );
}
if ( nodeName !== "input" && nodeName !== "option" ) {
migrateWarn("jQuery.fn.attr('value') no longer gets properties");
}
return name in elem ?
elem.value :
null;
},
set: function( elem, value ) {
var nodeName = ( elem.nodeName || "" ).toLowerCase();
if ( nodeName === "button" ) {
return valueAttrSet.apply( this, arguments );
}
if ( nodeName !== "input" && nodeName !== "option" ) {
migrateWarn("jQuery.fn.attr('value', val) no longer sets properties");
}
// Does not return so that setAttribute is also used
elem.value = value;
}
};
var matched, browser,
oldInit = jQuery.fn.init,
oldParseJSON = jQuery.parseJSON,
// Note this does NOT include the #9521 XSS fix from 1.7!
rquickExpr = /^(?:[^<]*(<[\w\W]+>)[^>]*|#([\w\-]*))$/;
// $(html) "looks like html" rule change
jQuery.fn.init = function( selector, context, rootjQuery ) {
var match;
if ( selector && typeof selector === "string" && !jQuery.isPlainObject( context ) &&
(match = rquickExpr.exec( selector )) && match[1] ) {
// This is an HTML string according to the "old" rules; is it still?
if ( selector.charAt( 0 ) !== "<" ) {
migrateWarn("$(html) HTML strings must start with '<' character");
}
// Now process using loose rules; let pre-1.8 play too
if ( context && context.context ) {
// jQuery object as context; parseHTML expects a DOM object
context = context.context;
}
if ( jQuery.parseHTML ) {
return oldInit.call( this, jQuery.parseHTML( jQuery.trim(selector), context, true ),
context, rootjQuery );
}
}
return oldInit.apply( this, arguments );
};
jQuery.fn.init.prototype = jQuery.fn;
// Let $.parseJSON(falsy_value) return null
jQuery.parseJSON = function( json ) {
if ( !json && json !== null ) {
migrateWarn("jQuery.parseJSON requires a valid JSON string");
return null;
}
return oldParseJSON.apply( this, arguments );
};
jQuery.uaMatch = function( ua ) {
ua = ua.toLowerCase();
var match = /(chrome)[ \/]([\w.]+)/.exec( ua ) ||
/(webkit)[ \/]([\w.]+)/.exec( ua ) ||
/(opera)(?:.*version|)[ \/]([\w.]+)/.exec( ua ) ||
/(msie) ([\w.]+)/.exec( ua ) ||
ua.indexOf("compatible") < 0 && /(mozilla)(?:.*? rv:([\w.]+)|)/.exec( ua ) ||
[];
return {
browser: match[ 1 ] || "",
version: match[ 2 ] || "0"
};
};
// Don't clobber any existing jQuery.browser in case it's different
if ( !jQuery.browser ) {
matched = jQuery.uaMatch( navigator.userAgent );
browser = {};
if ( matched.browser ) {
browser[ matched.browser ] = true;
browser.version = matched.version;
}
// Chrome is Webkit, but Webkit is also Safari.
if ( browser.chrome ) {
browser.webkit = true;
} else if ( browser.webkit ) {
browser.safari = true;
}
jQuery.browser = browser;
}
// Warn if the code tries to get jQuery.browser
migrateWarnProp( jQuery, "browser", jQuery.browser, "jQuery.browser is deprecated" );
jQuery.sub = function() {
function jQuerySub( selector, context ) {
return new jQuerySub.fn.init( selector, context );
}
jQuery.extend( true, jQuerySub, this );
jQuerySub.superclass = this;
jQuerySub.fn = jQuerySub.prototype = this();
jQuerySub.fn.constructor = jQuerySub;
jQuerySub.sub = this.sub;
jQuerySub.fn.init = function init( selector, context ) {
if ( context && context instanceof jQuery && !(context instanceof jQuerySub) ) {
context = jQuerySub( context );
}
return jQuery.fn.init.call( this, selector, context, rootjQuerySub );
};
jQuerySub.fn.init.prototype = jQuerySub.fn;
var rootjQuerySub = jQuerySub(document);
migrateWarn( "jQuery.sub() is deprecated" );
return jQuerySub;
};
// Ensure that $.ajax gets the new parseJSON defined in core.js
jQuery.ajaxSetup({
converters: {
"text json": jQuery.parseJSON
}
});
var oldFnData = jQuery.fn.data;
jQuery.fn.data = function( name ) {
var ret, evt,
elem = this[0];
// Handles 1.7 which has this behavior and 1.8 which doesn't
if ( elem && name === "events" && arguments.length === 1 ) {
ret = jQuery.data( elem, name );
evt = jQuery._data( elem, name );
if ( ( ret === undefined || ret === evt ) && evt !== undefined ) {
migrateWarn("Use of jQuery.fn.data('events') is deprecated");
return evt;
}
}
return oldFnData.apply( this, arguments );
};
var rscriptType = /\/(java|ecma)script/i,
oldSelf = jQuery.fn.andSelf || jQuery.fn.addBack;
jQuery.fn.andSelf = function() {
migrateWarn("jQuery.fn.andSelf() replaced by jQuery.fn.addBack()");
return oldSelf.apply( this, arguments );
};
// Since jQuery.clean is used internally on older versions, we only shim if it's missing
if ( !jQuery.clean ) {
jQuery.clean = function( elems, context, fragment, scripts ) {
// Set context per 1.8 logic
context = context || document;
context = !context.nodeType && context[0] || context;
context = context.ownerDocument || context;
migrateWarn("jQuery.clean() is deprecated");
var i, elem, handleScript, jsTags,
ret = [];
jQuery.merge( ret, jQuery.buildFragment( elems, context ).childNodes );
// Complex logic lifted directly from jQuery 1.8
if ( fragment ) {
// Special handling of each script element
handleScript = function( elem ) {
// Check if we consider it executable
if ( !elem.type || rscriptType.test( elem.type ) ) {
// Detach the script and store it in the scripts array (if provided) or the fragment
// Return truthy to indicate that it has been handled
return scripts ?
scripts.push( elem.parentNode ? elem.parentNode.removeChild( elem ) : elem ) :
fragment.appendChild( elem );
}
};
for ( i = 0; (elem = ret[i]) != null; i++ ) {
// Check if we're done after handling an executable script
if ( !( jQuery.nodeName( elem, "script" ) && handleScript( elem ) ) ) {
// Append to fragment and handle embedded scripts
fragment.appendChild( elem );
if ( typeof elem.getElementsByTagName !== "undefined" ) {
// handleScript alters the DOM, so use jQuery.merge to ensure snapshot iteration
jsTags = jQuery.grep( jQuery.merge( [], elem.getElementsByTagName("script") ), handleScript );
// Splice the scripts into ret after their former ancestor and advance our index beyond them
ret.splice.apply( ret, [i + 1, 0].concat( jsTags ) );
i += jsTags.length;
}
}
}
}
return ret;
};
}
var eventAdd = jQuery.event.add,
eventRemove = jQuery.event.remove,
eventTrigger = jQuery.event.trigger,
oldToggle = jQuery.fn.toggle,
oldLive = jQuery.fn.live,
oldDie = jQuery.fn.die,
ajaxEvents = "ajaxStart|ajaxStop|ajaxSend|ajaxComplete|ajaxError|ajaxSuccess",
rajaxEvent = new RegExp( "\\b(?:" + ajaxEvents + ")\\b" ),
rhoverHack = /(?:^|\s)hover(\.\S+|)\b/,
hoverHack = function( events ) {
if ( typeof( events ) !== "string" || jQuery.event.special.hover ) {
return events;
}
if ( rhoverHack.test( events ) ) {
migrateWarn("'hover' pseudo-event is deprecated, use 'mouseenter mouseleave'");
}
return events && events.replace( rhoverHack, "mouseenter$1 mouseleave$1" );
};
// Event props removed in 1.9, put them back if needed; no practical way to warn them
if ( jQuery.event.props && jQuery.event.props[ 0 ] !== "attrChange" ) {
jQuery.event.props.unshift( "attrChange", "attrName", "relatedNode", "srcElement" );
}
// Undocumented jQuery.event.handle was "deprecated" in jQuery 1.7
if ( jQuery.event.dispatch ) {
migrateWarnProp( jQuery.event, "handle", jQuery.event.dispatch, "jQuery.event.handle is undocumented and deprecated" );
}
// Support for 'hover' pseudo-event and ajax event warnings
jQuery.event.add = function( elem, types, handler, data, selector ){
if ( elem !== document && rajaxEvent.test( types ) ) {
migrateWarn( "AJAX events should be attached to document: " + types );
}
eventAdd.call( this, elem, hoverHack( types || "" ), handler, data, selector );
};
jQuery.event.remove = function( elem, types, handler, selector, mappedTypes ){
eventRemove.call( this, elem, hoverHack( types ) || "", handler, selector, mappedTypes );
};
jQuery.fn.error = function() {
var args = Array.prototype.slice.call( arguments, 0);
migrateWarn("jQuery.fn.error() is deprecated");
args.splice( 0, 0, "error" );
if ( arguments.length ) {
return this.bind.apply( this, args );
}
// error event should not bubble to window, although it does pre-1.7
this.triggerHandler.apply( this, args );
return this;
};
jQuery.fn.toggle = function( fn, fn2 ) {
// Don't mess with animation or css toggles
if ( !jQuery.isFunction( fn ) || !jQuery.isFunction( fn2 ) ) {
return oldToggle.apply( this, arguments );
}
migrateWarn("jQuery.fn.toggle(handler, handler...) is deprecated");
// Save reference to arguments for access in closure
var args = arguments,
guid = fn.guid || jQuery.guid++,
i = 0,
toggler = function( event ) {
// Figure out which function to execute
var lastToggle = ( jQuery._data( this, "lastToggle" + fn.guid ) || 0 ) % i;
jQuery._data( this, "lastToggle" + fn.guid, lastToggle + 1 );
// Make sure that clicks stop
event.preventDefault();
// and execute the function
return args[ lastToggle ].apply( this, arguments ) || false;
};
// link all the functions, so any of them can unbind this click handler
toggler.guid = guid;
while ( i < args.length ) {
args[ i++ ].guid = guid;
}
return this.click( toggler );
};
jQuery.fn.live = function( types, data, fn ) {
migrateWarn("jQuery.fn.live() is deprecated");
if ( oldLive ) {
return oldLive.apply( this, arguments );
}
jQuery( this.context ).on( types, this.selector, data, fn );
return this;
};
jQuery.fn.die = function( types, fn ) {
migrateWarn("jQuery.fn.die() is deprecated");
if ( oldDie ) {
return oldDie.apply( this, arguments );
}
jQuery( this.context ).off( types, this.selector || "**", fn );
return this;
};
// Turn global events into document-triggered events
jQuery.event.trigger = function( event, data, elem, onlyHandlers ){
if ( !elem && !rajaxEvent.test( event ) ) {
migrateWarn( "Global events are undocumented and deprecated" );
}
return eventTrigger.call( this, event, data, elem || document, onlyHandlers );
};
jQuery.each( ajaxEvents.split("|"),
function( _, name ) {
jQuery.event.special[ name ] = {
setup: function() {
var elem = this;
// The document needs no shimming; must be !== for oldIE
if ( elem !== document ) {
jQuery.event.add( document, name + "." + jQuery.guid, function() {
jQuery.event.trigger( name, null, elem, true );
});
jQuery._data( this, name, jQuery.guid++ );
}
return false;
},
teardown: function() {
if ( this !== document ) {
jQuery.event.remove( document, name + "." + jQuery._data( this, name ) );
}
return false;
}
};
}
);
})( jQuery, window );

File diff suppressed because one or more lines are too long

10337
web/components/jquery/jquery.js vendored Normal file

File diff suppressed because it is too large Load diff

4
web/components/jquery/jquery.min.js vendored Normal file

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,17 @@
var components = {
"packages": [
{
"name": "jquery",
"main": "jquery-built.js"
}
],
"baseUrl": "components"
};
if (typeof require !== "undefined" && require.config) {
require.config(components);
} else {
var require = components;
}
if (typeof exports !== "undefined" && typeof module !== "undefined") {
module.exports = components;
}

View file

2036
web/components/require.js Normal file

File diff suppressed because it is too large Load diff

5
web/index.php Normal file
View file

@ -0,0 +1,5 @@
<?php
$app = require __DIR__.'/../app/bootstrap.php';
$app->run();