Finished auto pdf generation

This commit is contained in:
R. Eric Wheeler 2016-07-17 17:02:58 -07:00
parent 4a3223307c
commit d2659172f7
21 changed files with 912 additions and 484 deletions

View File

@ -13,7 +13,6 @@ use Sikofitt\Config\ConfigTrait;
use Sikofitt\Json\JsonFileTrait; use Sikofitt\Json\JsonFileTrait;
use Sikofitt\Json\JsonTrait; use Sikofitt\Json\JsonTrait;
use Silex\Application; use Silex\Application;
use Symfony\Component\HttpFoundation\Request;
require __DIR__ . '/../vendor/autoload.php'; require __DIR__ . '/../vendor/autoload.php';
@ -33,6 +32,15 @@ class App extends Application
use Application\UrlGeneratorTrait; use Application\UrlGeneratorTrait;
private $debug; private $debug;
/**
* @return string
*/
public function getConfDirectory()
{
return $this->getAppDirectory() . '/config';
}
/** /**
* Returns the application directory. * Returns the application directory.
* *
@ -45,6 +53,22 @@ class App extends Application
return dirname($r->getFileName()); return dirname($r->getFileName());
} }
/**
* @return string
*/
public function getResumeJson()
{
return $this->getDataDirectory() . '/resume.json';
}
/**
* @return string
*/
public function getDataDirectory()
{
return $this->getRootDirectory() . '/data';
}
/** /**
* Returns the root directory of the application. * Returns the root directory of the application.
* *
@ -56,30 +80,6 @@ class App extends Application
return dirname($this->getAppDirectory()); return dirname($this->getAppDirectory());
} }
/**
* @return string
*/
public function getConfDirectory()
{
return $this->getAppDirectory() . '/config';
}
/**
* @return string
*/
public function getDataDirectory()
{
return $this->getRootDirectory() . '/data';
}
/**
* @return string
*/
public function getResumeJson()
{
return $this->getDataDirectory() . '/resume.json';
}
/** /**
* @return string * @return string
*/ */
@ -93,15 +93,11 @@ class App extends Application
return $this->getDataDirectory() . '/logs'; return $this->getDataDirectory() . '/logs';
} }
/** public function getDebug()
* Registers media icons
*
* @param \Sikofitt\Image\Profile\ProfileIconInterface $icon
*/
public function registerIcon(\Sikofitt\Image\Profile\ProfileIconInterface $icon)
{ {
$this->config(sprintf('app.icons.%s', $icon->getName()), ['icon' => $icon->getIcon(), 'url' => $icon->getDefaultUrl()]); return $this['debug'];
} }
public function setDebug() public function setDebug()
{ {
$this['debug'] = (null !== $this->config('app.debug') ? $this->config('app.debug') : true); $this['debug'] = (null !== $this->config('app.debug') ? $this->config('app.debug') : true);
@ -111,10 +107,14 @@ class App extends Application
} }
} }
public function getDebug() public function boot()
{ {
return $this['debug']; $this->registerExtenders();
// register default icons
$this->registerDefaultIcons();
return parent::boot();
} }
public function registerExtenders() public function registerExtenders()
{ {
if (!$this['debug']) { if (!$this['debug']) {
@ -137,7 +137,7 @@ class App extends Application
'requestedMethod' => $request->getMethod(), 'requestedMethod' => $request->getMethod(),
'code' => $code, 'code' => $code,
]; ];
if($request->isXmlHttpRequest()) { if ($request->isXmlHttpRequest()) {
$message = json_encode($message); $message = json_encode($message);
} else { } else {
$message = $this->renderView('error.405.html.twig', $message); $message = $this->renderView('error.405.html.twig', $message);
@ -145,12 +145,18 @@ class App extends Application
$this->log($e->getMessage(), ['code' => $code], \Symfony\Bridge\Monolog\Logger::WARNING); $this->log($e->getMessage(), ['code' => $code], \Symfony\Bridge\Monolog\Logger::WARNING);
break; break;
case 500: case 500:
$message = json_encode(['status' => 'error', 'message' => 'Critical Error', 'code' => $code]); $message = ['status' => 'error', 'message' => 'Critical Error', 'code' => $code];
if ($request->isXmlHttpRequest()) {
$message = json_decode($message);
} else {
$message = $this->renderView('error.html.twig', $message);
}
$this->log($e->getMessage(), ['code' => $code], \Symfony\Bridge\Monolog\Logger::CRITICAL); $this->log($e->getMessage(), ['code' => $code], \Symfony\Bridge\Monolog\Logger::CRITICAL);
break; break;
default: default:
$message = ['status' => 'error', 'message' => $e->getMessage(), 'code' => $code, 'requestUri' => $request->getRequestUri()]; $message = ['status' => 'error', 'message' => $e->getMessage(), 'code' => $code, 'requestUri' => $request->getRequestUri()];
if($request->isXmlHttpRequest()) { if ($request->isXmlHttpRequest()) {
$message = json_decode($message); $message = json_decode($message);
} else { } else {
$message = $this->renderView('error.html.twig', $message); $message = $this->renderView('error.html.twig', $message);
@ -174,13 +180,6 @@ class App extends Application
return $twig; return $twig;
}); });
} }
public function boot()
{
$this->registerExtenders();
// register default icons
$this->registerDefaultIcons();
return parent::boot();
}
public function registerDefaultIcons() public function registerDefaultIcons()
{ {
@ -190,4 +189,14 @@ class App extends Application
$this->registerIcon(new \Sikofitt\Image\Profile\GitlabProfileIcon()); $this->registerIcon(new \Sikofitt\Image\Profile\GitlabProfileIcon());
$this->registerIcon(new \Sikofitt\Image\Profile\LinkedinProfileIcon()); $this->registerIcon(new \Sikofitt\Image\Profile\LinkedinProfileIcon());
} }
/**
* Registers media icons
*
* @param \Sikofitt\Image\Profile\ProfileIconInterface $icon
*/
public function registerIcon(\Sikofitt\Image\Profile\ProfileIconInterface $icon)
{
$this->config(sprintf('app.icons.%s', $icon->getName()), ['icon' => $icon->getIcon(), 'url' => $icon->getDefaultUrl()]);
}
} }

View File

@ -29,9 +29,9 @@ use Silex\Provider\{
VarDumperServiceProvider, VarDumperServiceProvider,
WebProfilerServiceProvider WebProfilerServiceProvider
}; };
use Symfony\Bridge\Monolog\Logger;
use Symfony\Component\Debug\ErrorHandler; use Symfony\Component\Debug\ErrorHandler;
use Symfony\Component\Debug\ExceptionHandler; use Symfony\Component\Debug\ExceptionHandler;
use Symfony\Bridge\Monolog\Logger;
use WhoopsPimple\WhoopsServiceProvider; use WhoopsPimple\WhoopsServiceProvider;
$app->register(new ConfigServiceProvider(), [ $app->register(new ConfigServiceProvider(), [
@ -57,8 +57,9 @@ $app
->register(new LocaleServiceProvider()) ->register(new LocaleServiceProvider())
->register(new TranslationServiceProvider()) ->register(new TranslationServiceProvider())
->register(new ValidatorServiceProvider()) ->register(new ValidatorServiceProvider())
->register(new CsrfServiceProvider()) ->register(new CsrfServiceProvider());
->register(new MonologServiceProvider(), [
$app->register(new MonologServiceProvider(), [
//'monolog.logfile' => sprintf('%s/%s.log', $app->getLogDirectory(), $app['env']), //'monolog.logfile' => sprintf('%s/%s.log', $app->getLogDirectory(), $app['env']),
'monolog.name' => 'Resume.PHP', 'monolog.name' => 'Resume.PHP',
'monolog.logfile' => 'php://stderr', 'monolog.logfile' => 'php://stderr',
@ -72,7 +73,7 @@ $app
'console.project_directory' => $app->getAppDirectory(), 'console.project_directory' => $app->getAppDirectory(),
]); ]);
$app['swiftmailer.use_spool'] = false; $app['swiftmailer.use_spool'] = false;
if(false === getenv('SPARKPOST_API_KEY') && null !== $app->config('app.smtp_host')) { if (false === getenv('SPARKPOST_API_KEY') && null !== $app->config('app.smtp_host')) {
$app['swiftmailer.options'] = [ $app['swiftmailer.options'] = [
'host' => $app->config('app.smtp_host'), 'host' => $app->config('app.smtp_host'),
'port' => $app->config('app.smtp_post'), 'port' => $app->config('app.smtp_post'),

View File

@ -31,12 +31,14 @@
{% block body %} {% block body %}
{% endblock %} {% endblock %}
</div> </div>
{% if renderPdf == false %}
<footer id="footer" style=" <footer id="footer" style="
margin-top: 100px; margin-top: 100px;
height: 150px; height: 150px;
background-color: #000; background-color: #000;
"> ">
</footer> </footer>
{% endif %}
<script src="{{ baseUrl ~ asset('js/resume.min.js') }}"></script> <script src="{{ baseUrl ~ asset('js/resume.min.js') }}"></script>
{% block javascripts_foot %}{% endblock %} {% block javascripts_foot %}{% endblock %}
{% block inline_js_foot %}{% endblock %} {% block inline_js_foot %}{% endblock %}

View File

@ -0,0 +1,66 @@
{% extends 'base.html.twig' %}
{% block body %}
<div class="uk-container uk-container-center">
<h1 class="uk-text-center uk-heading-large">{{ basics.name|title }}</h1>
<h2 class="uk-text-center">{{ basics.label }}</h2>
{% if basics.summary is defined and basics.summary is not empty %}
<p class="uk-text-lead uk-text-center">{{ basics.summary|raw }}</p>
{% endif %}
<h2 class="uk-text-center">Contact</h2>
<div class="uk-grid uk-grid-match" data-uk-grid-margin="">
<div class="uk-width-1-2 uk-row-first">
<h3 class="uk-text-center"></h3>
</div>
<div class="uk-width-1-2">
<p class="uk-text-left uk-align-left uk-text-top"></p>
</div>
</div> <!-- /uk-grid-match -->
<div class="uk-grid uk-grid-match" data-uk-grid-margin="">
<div class="uk-width-1-2 uk-row-first">
<h3 class="uk-text-center">Email</h3>
</div>
<div class="uk-width-1-2">
<p class="uk-text-left uk-align-left uk-text-top">{{ basics.email }}</p>
</div>
</div> <!-- /uk-grid-match -->
<div class="uk-grid uk-grid-match" data-uk-grid-margin="">
<div class="uk-width-1-2 uk-row-first">
<h3 class="uk-text-center">Phone</h3>
</div>
<div class="uk-width-1-2">
<p class="uk-text-left">{{ basics.phone }}</p>
</div>
</div><!-- /uk-grid-match -->
<div class="uk-grid uk-grid-match" data-uk-grid-margin="">
<div class="uk-width-1-2 uk-row-first">
<h3 class="uk-text-center">Web</h3>
</div>
<div class="uk-width-1-2">
<p class="uk-text-left">
<span class="uk-display-inline-block uk-margin-small-top uk-margin-small-bottom">
<a class="profile-link" href="{{ basics.website }}" title="Home page">
<span class="uk-margin-small-right">
<img class="uk-thumbnail uk-thumbnail-mini uk-border-circle" src="{{ basics.picture }}" alt="Me" />
</span>
{{ basics.website }}
</a>
</span><br />
{% for profile in basics.profiles %}
<span class="uk-display-inline-block uk-margin-small-top uk-margin-small-bottom">{{ render_profile(profile, true)|raw }}</span><br />
{% endfor %}
</p>
</div>
</div><!-- /uk-grid-match -->
</div>
</div><!-- /.uk-container.uk-container-center -->
{% endblock %}

View File

@ -5,18 +5,10 @@
{% endblock %} {% endblock %}
{% block body %} {% block body %}
<style type="text/css">
.uk-sticky-active-sidebar {
margin-top: 35px;
}
div#sidebar.uk-active {
margin-top: 35px;
transition: margin-top 2s;
}
</style>
<div class="uk-grid" data-uk-grid-margin xmlns="http://www.w3.org/1999/html"> <div class="uk-grid" data-uk-grid-margin xmlns="http://www.w3.org/1999/html">
{% if renderPdf == false %}
<div class="uk-width-1-1"> <div class="uk-width-1-1">
<h1 class="uk-heading-large"> <h1 class="uk-heading-large">
{% if basics.name is not empty %} {% if basics.name is not empty %}
{{ basics.name|title }} {{ basics.name|title }}
@ -33,6 +25,8 @@
{% endif %} {% endif %}
</div> </div>
{% endif %}
</div> </div>
<div class="uk-grid" data-uk-grid-margin> <div class="uk-grid" data-uk-grid-margin>
@ -41,6 +35,7 @@
{% include 'work.html.twig' %} {% include 'work.html.twig' %}
{% include 'references.html.twig' %} {% include 'references.html.twig' %}
</div> </div>
{% if renderPdf == false %}
<div class="uk-width-medium-1-4"> <div class="uk-width-medium-1-4">
<div id="sidebar" class="uk-panel uk-panel-header uk-panel-box" <div id="sidebar" class="uk-panel uk-panel-header uk-panel-box"
data-uk-sticky="{top:35, animation: 'uk-animation-slide-top', getWidthFrom:'#sidebar'}"> data-uk-sticky="{top:35, animation: 'uk-animation-slide-top', getWidthFrom:'#sidebar'}">
@ -54,13 +49,22 @@
<ul class="uk-list uk-list-line"> <ul class="uk-list uk-list-line">
{% if app.config.app.phone is not empty %} {% if app.config.app.phone is not empty %}
{% if renderPdf == true %}
<li class="uk-list-space"><a href="tel:{{ app.config.app.phone }}"
title="Telephone">{{ app.config.app.phone }}</a></li>
{% else %}
<li class="uk-list-space"><a href="#phone-modal" class="hidden-phone" data-uk-modal>Phone</a></li> <li class="uk-list-space"><a href="#phone-modal" class="hidden-phone" data-uk-modal>Phone</a></li>
{% endif %} {% endif %}
{% endif %}
<li class="uk-list-space"> <li class="uk-list-space">
{% if basics.email is not empty %} {% if basics.email is not empty %}
{% if renderPdf == true %}
<a href="mailto:{{ basics.email }}" title="Send email">{{ basics.email }}</a>
{% else %}
<a href="#contact-form-wrapper" data-uk-modal>{{ basics.email }}</a> <a href="#contact-form-wrapper" data-uk-modal>{{ basics.email }}</a>
{% endif %} {% endif %}
{% endif %}
</li> </li>
@ -69,6 +73,7 @@
<a href="{{ basics.website }}" target="_blank" title="Home page">{{ basics.website }}</a> <a href="{{ basics.website }}" target="_blank" title="Home page">{{ basics.website }}</a>
</li> </li>
{% endif %} {% endif %}
<li><a href="{{ baseUrl ~ asset('pdf') }}" title="Download Resume in PDF format">Download PDF</a></li>
{% if basics.location|length > 0 and basics.location is not empty %} {% if basics.location|length > 0 and basics.location is not empty %}
<li class="uk-list-space"> <li class="uk-list-space">
<address> <address>
@ -104,6 +109,7 @@
</div> </div>
{% endif %}
</div> </div>
{% include 'contact.html.twig' %} {% include 'contact.html.twig' %}

View File

@ -7,6 +7,5 @@
<small>{{ reference.name }}</small> <small>{{ reference.name }}</small>
</blockquote> </blockquote>
{% endfor %} {% endfor %}
{% endif %}
</div> </div>
{% endif %}

View File

@ -1,5 +1,18 @@
{% if skills is defined and skills is not empty %} {% if skills is defined and skills is not empty %}
{% if renderPdf == true %}
<div style="position:relative; top:0; left:0;"
class="uk-grid uk-width-1-1 uk-grid-match uk-grid-width-1-3 uk-margin-large-top">
<div class="uk-text-left">{{ basics.phone }}</div>
<div class="uk-text-center">{{ basics.email }}</div>
<div class="uk-text-right">{{ basics.website }}</div>
</div>
<div style="position:absolute; bottom:0; left:0; border-top:1px solid #ddd; padding-top:6px;"
class="uk-container uk-container-center uk-grid uk-width-1-1 uk-grid-match uk-grid-width-1-3 uk-margin-large-top">
<div class="uk-text-left">{{ basics.phone }}</div>
<div class="uk-text-center">{{ basics.email }}</div>
<div class="uk-text-right">{{ basics.website }}</div>
</div>
{% endif %}
<h1 id="skills">Skills</h1> <h1 id="skills">Skills</h1>
<dl class="uk-description-list-line"> <dl class="uk-description-list-line">
{% for skill in skills %} {% for skill in skills %}

View File

@ -45,7 +45,7 @@
"startDate": "2007-05-27", "startDate": "2007-05-27",
"summary": "I have done many different things during my employment at Stanford University.<br />A few of things things have been : Desktop Support, Computer cluster administration for the NSF and NNIN using Scyld Clusterware, General programing/debugging and frontend as well as backend web development using a number of different technologies.", "summary": "I have done many different things during my employment at Stanford University.<br />A few of things things have been : Desktop Support, Computer cluster administration for the NSF and NNIN using Scyld Clusterware, General programing/debugging and frontend as well as backend web development using a number of different technologies.",
"highlights": [ "highlights": [
"Repaired and Maintained a 64 node research computing cluster for the <a href=\\\"//nnin.org\\\" title=\\\"National Nanotechnology Infrastructure Network\\\">NNIN</a> funded by the <a href=\\\"//nsf.gov\\\" title=\\\"National Science Foundation\\\">NSF</a>.", "Repaired and Maintained a 64 node research computing cluster for the <a href=\"http://nnin.org\" rel=\"ext\" title=\"National Nanotechnology Infrastructure Network\">NNIN</a> funded by the <a href=\"https://nsf.gov\" rel=\"ext\" title=\"National Science Foundation\">NSF</a>.",
"Converted Electrical Engineering's static web site to Drupal 7.", "Converted Electrical Engineering's static web site to Drupal 7.",
"Created many Drupal 7 modules for the EE website including a custom events, lecture and committe minutes content type, a CDN module for uploading and recieving images/videos/documents, an Orglist module for displaying Faculty, Students, and Staff from a custom API.", "Created many Drupal 7 modules for the EE website including a custom events, lecture and committe minutes content type, a CDN module for uploading and recieving images/videos/documents, an Orglist module for displaying Faculty, Students, and Staff from a custom API.",
"Created a custom Drupal 7 site for the SystemX organization with integrated login for affiliates as well as Stanford staff." "Created a custom Drupal 7 site for the SystemX organization with integrated login for affiliates as well as Stanford staff."
@ -156,14 +156,5 @@
] ]
} }
], ],
"references": [ "references": []
{
"name": "Craig Stadler",
"reference": "It is my pleasure to recommend Richard, his performance working as a consultant for Main St. Company proved that he will be a valuable addition to any company."
},
{
"name": "John Doe",
"reference": "Richard is awesome and cool."
}
]
} }

View File

@ -1,4 +1,14 @@
<?php <?php
/*
* This file is part of Resume.PHP.
*
* (copyleft) R. Eric Wheeler <sikofitt@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/** /**
* Created by PhpStorm. * Created by PhpStorm.
* User: eric * User: eric
@ -8,13 +18,14 @@
namespace Sikofitt\Command; namespace Sikofitt\Command;
use Knp\Command\Command; use Knp\Command\Command;
use Knp\Snappy\Pdf;
use Sikofitt\Resume\ResumeParser;
use Symfony\Component\Console\Input\InputInterface; use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Output\OutputInterface; use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style; use Symfony\Component\Console\Style;
use Knp\Snappy\Pdf; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\Process\Process;
/** /**
* Class CreatePdfCommand * Class CreatePdfCommand
@ -26,7 +37,7 @@ class CreatePdfCommand extends Command
/** /**
* *
*/ */
public function configure () public function configure()
{ {
$this->setName('resume:pdf:create') $this->setName('resume:pdf:create')
->setDescription('Create a pdf of your website.') ->setDescription('Create a pdf of your website.')
@ -37,20 +48,26 @@ class CreatePdfCommand extends Command
* @param InputInterface $input * @param InputInterface $input
* @param OutputInterface $output * @param OutputInterface $output
*/ */
public function interact (InputInterface $input, OutputInterface $output) public function interact(InputInterface $input, OutputInterface $output)
{ {
$index = $this->getSilexApplication()->renderView('index.html.twig', ['renderPdf' => true]); $process = new Process('hostname');
$pdf = new Pdf($this->getSilexApplication()->getRootDirectory() . '/vendor/bin/wkhtmltopdf-amd64'); $process->run();
$pdf->generateFromHtml($index, $this->getSilexApplication()->getRootDirectory() . '/resume.pdf', ['keep-relative-links' => false, 'resolve-relative-links' => false, 'load-error-handling' => false, 'disable-javascript' => true, 'disable-external-links' => true, 'disable-internal-links' => true]); $hostname = $process->getOutput();
$resume = new ResumeParser($this->getSilexApplication());
$request = Request::createFromGlobals();
$pdf = new \Sikofitt\Resume\Pdf($this->getSilexApplication(), $request);
$pdf->setHost(sprintf('http://%s', $hostname));
$data = $pdf->render();
file_put_contents($this->getSilexApplication()->getRootDirectory() . '/resume1.pdf', $data);
} }
/** /**
* @param InputInterface $input * @param InputInterface $input
* @param OutputInterface $output * @param OutputInterface $output
*/ */
public function execute (InputInterface $input, OutputInterface $output) public function execute(InputInterface $input, OutputInterface $output)
{ {
} }
} }

View File

@ -30,12 +30,14 @@ use Symfony\Component\Validator\Constraints\{
* *
* @package Sikofitt\Config * @package Sikofitt\Config
*/ */
class ConfigServiceProvider implements ServiceProviderInterface, BootableProviderInterface { class ConfigServiceProvider implements ServiceProviderInterface, BootableProviderInterface
{
/** /**
* @param Container $app * @param Container $app
*/ */
public function register(Container $app) { public function register(Container $app)
{
$app['config'] = function ($app) { $app['config'] = function ($app) {
$config = Config::load($app['config.path']); $config = Config::load($app['config.path']);
@ -43,7 +45,8 @@ class ConfigServiceProvider implements ServiceProviderInterface, BootableProvide
}; };
} }
public function boot(Application $app) { public function boot(Application $app)
{
$configItems = [ $configItems = [
'email' => $app->config('app.email'), 'email' => $app->config('app.email'),
'phone' => $app->config('app.phone'), 'phone' => $app->config('app.phone'),
@ -90,6 +93,5 @@ class ConfigServiceProvider implements ServiceProviderInterface, BootableProvide
if (count($validationErrors) > 0) { if (count($validationErrors) > 0) {
throw new MissingConfigurationItemException($validationErrors[0]); throw new MissingConfigurationItemException($validationErrors[0]);
} }
} }
} }

View File

@ -24,7 +24,6 @@
namespace Sikofitt\Controller; namespace Sikofitt\Controller;
use ReCaptcha\ReCaptcha; use ReCaptcha\ReCaptcha;
use Sikofitt\Form\Type\ContactType; use Sikofitt\Form\Type\ContactType;
use Silex\Api\ControllerProviderInterface; use Silex\Api\ControllerProviderInterface;
@ -44,7 +43,8 @@ use Symfony\Component\Validator\Constraints\Type;
* *
* @package Sikofitt\Controller * @package Sikofitt\Controller
*/ */
class ApiControllerProvider implements ControllerProviderInterface { class ApiControllerProvider implements ControllerProviderInterface
{
/** /**
* {@inheritdoc} * {@inheritdoc}
@ -53,7 +53,8 @@ class ApiControllerProvider implements ControllerProviderInterface {
* *
* @return mixed * @return mixed
*/ */
public function connect(Application $app) { public function connect(Application $app)
{
$controllers = $app['controllers_factory']; $controllers = $app['controllers_factory'];
$controllers->get('/v1/schema', function () use ($app) { $controllers->get('/v1/schema', function () use ($app) {
@ -136,10 +137,7 @@ class ApiControllerProvider implements ControllerProviderInterface {
'const' => $valid[0]->getCode(), 'const' => $valid[0]->getCode(),
'code' => 256, 'code' => 256,
], 256); ], 256);
} else {
}
else {
$contactFormName = $contactFormData['contact']['name']; $contactFormName = $contactFormData['contact']['name'];
$contactFormEmail = $contactFormData['contact']['email']; $contactFormEmail = $contactFormData['contact']['email'];
$contactFormMessage = sprintf("%s\n\nEmail : %s <%s>", $contactFormData['contact']['message'], $contactFormName, $contactFormEmail); $contactFormMessage = sprintf("%s\n\nEmail : %s <%s>", $contactFormData['contact']['message'], $contactFormName, $contactFormEmail);
@ -162,8 +160,7 @@ class ApiControllerProvider implements ControllerProviderInterface {
'data' => $contactFormData, 'data' => $contactFormData,
'sent' => $sent, 'sent' => $sent,
], 201); ], 201);
} } else {
else {
return new JsonResponse([ return new JsonResponse([
'status' => 'error', 'status' => 'error',
'message' => 'There was an error sending the message.', 'message' => 'There was an error sending the message.',
@ -176,7 +173,7 @@ class ApiControllerProvider implements ControllerProviderInterface {
})->method('POST')->bind('api_message'); })->method('POST')->bind('api_message');
$controllers->post('/v1/update', function(Request $request) use ($app) { $controllers->post('/v1/update', function (Request $request) use ($app) {
return new JsonResponse($app->decodeFile($app->getResumeSchema())); return new JsonResponse($app->decodeFile($app->getResumeSchema()));
})->bind('api_update'); })->bind('api_update');
@ -196,8 +193,7 @@ class ApiControllerProvider implements ControllerProviderInterface {
'phone' => null !== $app->config('app.phone') ? $app->config('app.phone') : 'No phone has been set in the configuration. Please let the owner know.', 'phone' => null !== $app->config('app.phone') ? $app->config('app.phone') : 'No phone has been set in the configuration. Please let the owner know.',
], ],
]; ];
} } else {
else {
$errorCodes = [ $errorCodes = [
'missing-input-secret' => 'The secret parameter is missing.', 'missing-input-secret' => 'The secret parameter is missing.',
'invalid-input-secret' => 'The secret parameter is invalid or malformed.', 'invalid-input-secret' => 'The secret parameter is invalid or malformed.',

View File

@ -11,186 +11,92 @@
namespace Sikofitt\Controller; namespace Sikofitt\Controller;
use ReCaptcha\ReCaptcha; use Sikofitt\{
use Sikofitt\Form\Type\ContactType; Resume\Pdf,
use Silex\Api\ControllerProviderInterface; Form\Type\ContactType,
use Silex\Application; Resume\ResumeParser
use Symfony\Component\Form\Form; };
use Symfony\Component\Form\FormFactory; use Silex\{
use Symfony\Component\HttpFoundation\JsonResponse; Api\ControllerProviderInterface,
use Symfony\Component\HttpFoundation\Request; Application,
use Knp\Snappy\Pdf; ControllerCollection
use Symfony\Component\HttpFoundation\Response; };
use Symfony\Component\HttpFoundation\ {
Request,
Response,
ResponseHeaderBag,
StreamedResponse
};
/** /**
* Class ResumeControllerProvider * Class ResumeControllerProvider
*
* @package Sikofitt\Controller * @package Sikofitt\Controller
*/ */
class ResumeControllerProvider implements ControllerProviderInterface class ResumeControllerProvider implements ControllerProviderInterface
{ {
/** /**
* @var object * @var ResumeParser
*/ */
private $resumeData; private $resume;
/** /**
* @param Application $app * @param Application $app
* @return mixed *
* @return ControllerCollection
*/ */
public function connect(Application $app) public function connect(Application $app)
{ {
$this->resumeData = $app->decodeFile( $this->resume = new ResumeParser($app);
$app->getDataDirectory() . '/resume.json',
$app->getDataDirectory() . '/schema/schema.v1.json' $controllers = $app['controllers_factory'];
);
$controllers->get('/', function (Request $request) use ($app) {
$contactForm = $app['form.factory']->create(ContactType::class); $contactForm = $app['form.factory']->create(ContactType::class);
$controllers = $app['controllers_factory'];
$controllers->get('/pdf', function (Request $request) use ($app) {
$pdf = new Pdf($app->getRootDirectory() . '/vendor/bin/wkhtmltopdf-amd64');
$content = $app->render('index.html.twig', [
'renderPdf' => true,
'baseUrl' => $request->getSchemeAndHttpHost(),
'fullData' => $this->resumeData,
'basics' => $this->getResumeBasics(),
'work' => $this->getResumeWork(),
'volunteer' => $this->getResumeVolunteer(),
'education' => $this->getResumeEducation(),
'awards' => $this->getResumeAwards(),
'publications' => $this->getResumePublications(),
'skills' => $this->getResumeSkills(),
'languages' => $this->getResumeLanguages(),
'interests' => $this->getResumeInterests(),
'references' => $this->getResumeReferences(),
]);
$renderedPdf = $pdf->getOutputFromHtml($content->getContent());
$response = new Response();
$response->setContent($renderedPdf);
$response->setStatusCode(200);
$response->headers->set('Content-Type', 'application/pdf');
$response->headers->set('Content-Disposition', 'attachment; filename="resume.pdf');
return $response;
});
$controllers->get('/', function (Request $request) use ($app, $contactForm) {
return $app['twig']->render('index.html.twig', [ return $app['twig']->render('index.html.twig', [
'renderPdf' => false, 'renderPdf' => false,
'baseUrl' => $request->getSchemeAndHttpHost(), 'baseUrl' => $request->getSchemeAndHttpHost(),
'fullData' => $this->resumeData, 'basics' => $this->resume->getBasics(),
'basics' => $this->getResumeBasics(), 'work' => $this->resume->getWork(),
'work' => $this->getResumeWork(), 'volunteer' => $this->resume->getVolunteer(),
'volunteer' => $this->getResumeVolunteer(), 'education' => $this->resume->getEducation(),
'education' => $this->getResumeEducation(), 'awards' => $this->resume->getAwards(),
'awards' => $this->getResumeAwards(), 'publications' => $this->resume->getPublications(),
'publications' => $this->getResumePublications(), 'skills' => $this->resume->getSkills(),
'skills' => $this->getResumeSkills(), 'languages' => $this->resume->getLanguages(),
'languages' => $this->getResumeLanguages(), 'interests' => $this->resume->getInterests(),
'interests' => $this->getResumeInterests(), 'references' => $this->resume->getReferences(),
'references' => $this->getResumeReferences(),
'contact_form' => $contactForm->createView(), 'contact_form' => $contactForm->createView(),
]); ]);
}); });
$controllers->post('/', function(Request $request) use ($app) { $controllers->get('/pdf', function (Request $request) use ($app) {
$contactFormData = $request->request->all();
$contactFormName = $contactFormData['contact']['name']; $pdf = new Pdf($app, $request);
$contactFormEmail = $contactFormData['contact']['email'];
$contactFormMessage = $contactFormData['contact']['message']; $renderedPdf = $pdf->render();
$sent = $app['mailer']->send(\Swift_Message::newInstance()
->setSubject('[Resume] Message') $filename = str_replace('@', '-AT-', $this->resume->getBasics()->email);
->setFrom([$contactFormEmail => $contactFormName])
->setTo($app->config('app.email')) $response = new StreamedResponse();
->setBody($contactFormMessage) $response->setCallback(function () use ($renderedPdf) {
, $failures); print $renderedPdf;
dump($failures);
dump($sent);
return new JsonResponse(['failures' => $failures, 'sent' => (bool)$sent]);
}); });
$response->setStatusCode(Response::HTTP_OK);
$disposition = $response->headers->makeDisposition(
ResponseHeaderBag::DISPOSITION_ATTACHMENT,
$filename . '.pdf'
);
$response->headers->set('Content-Disposition', $disposition);
$response->send();
});
return $controllers; return $controllers;
} }
/**
* @return null
*/
public function getResumeBasics()
{
return (isset($this->resumeData->basics) && count($this->resumeData->basics) > 0) ? $this->resumeData->basics : null;
}
/**
* @return null
*/
public function getResumeWork()
{
return (isset($this->resumeData->work) && count($this->resumeData->work) > 0) ? $this->resumeData->work : null;
}
/**
* @return null
*/
public function getResumeVolunteer()
{
return (isset($this->resumeData->volunteer) && count($this->resumeData->volunteer) > 0) ? $this->resumeData->volunteer : null;
}
/**
* @return null
*/
public function getResumeEducation()
{
return (isset($this->resumeData->education) && count($this->resumeData->education) > 0) ? $this->resumeData->education : null;
}
/**
* @return null
*/
public function getResumeAwards()
{
return (isset($this->resumeData->awards) && count($this->resumeData->awards) > 0) ? $this->resumeData->awards : null;
}
/**
* @return null
*/
public function getResumePublications()
{
return (isset($this->resumeData->publications) && count($this->resumeData->publications) > 0) ? $this->resumeData->publications : null;
}
/**
* @return null
*/
public function getResumeSkills()
{
return (isset($this->resumeData->skills) && count($this->resumeData->skills) > 0) ? $this->resumeData->skills : null;
}
/**
* @return null
*/
public function getResumeLanguages()
{
return (isset($this->resumeData->languages) && count($this->resumeData->languages) > 0) ? $this->resumeData->languages : null;
}
/**
* @return null
*/
public function getResumeInterests()
{
return (isset($this->resumeData->interests) && count($this->resumeData->interests) > 0) ? $this->resumeData->interests : null;
}
/**
* @return null
*/
public function getResumeReferences()
{
return (isset($this->resumeData->references) && count($this->resumeData->references) > 0) ? $this->resumeData->references : null;
}
} }

View File

@ -38,7 +38,6 @@ class ContactType extends AbstractType
*/ */
public function buildForm(FormBuilderInterface $builder, array $options) public function buildForm(FormBuilderInterface $builder, array $options)
{ {
$builder $builder
->setAction('/api/v1/message') ->setAction('/api/v1/message')
->add('name', TextType::class, [ ->add('name', TextType::class, [

147
src/Sikofitt/Resume/Pdf.php Normal file
View File

@ -0,0 +1,147 @@
<?php
/*
* This file is part of Resume.PHP.
*
* (copyleft) R. Eric Wheeler <sikofitt@gmail.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
/**
* Created by PhpStorm.
* User: eric
* Date: 7/17/16
* Time: 3:26 PM
*/
namespace Sikofitt\Resume;
use App;
use Knp\Snappy\Pdf as Snappy;
use Symfony\Component\HttpFoundation\Request;
class Pdf
{
/**
* @var string
*/
private $host;
/**
* @var Snappy
*/
private $snappy;
/**
* @var App
*/
private $app;
/**
* @var Request
*/
private $request;
/**
* @var ResumeParser
*/
private $resume;
/**
* Pdf constructor.
*
* @param App $app
* @param Request $request
*/
public function __construct(App $app, Request $request)
{
$this->app = $app;
$this->request = $request;
$this->resume = new ResumeParser($this->app);
$this->snappy = new Snappy($app->getRootDirectory() . '/vendor/bin/wkhtmltopdf-amd64');
}
/**
* @return string
*/
public function render()
{
$content = $this->getIndex();
$cover = $this->getCover();
$title = $this->getTitle();
return $this->snappy->getOutputFromHtml(
$content, [
'lowquality' => false,
'collate' => true,
'margin-right' => 1,
'margin-left' => 1,
'title' => $title,
'header-font-size' => 12,
'cover' => $cover,
'no-stop-slow-scripts' => true,
'stop-slow-scripts' => false,
'enable-javascript' => true,
'no-outline' => true,
'outline' => false,
'dump-outline' => $this->app->getLogDirectory() . '/pdf-outline-' . md5(time()),
]
);
}
/**
* @return string
*/
public function getIndex()
{
$host = $this->getHost() ?: $this->request->getSchemeAndHttpHost();
return $this->app->renderView('index.html.twig', [
'renderPdf' => true,
'baseUrl' => $host,
'basics' => $this->resume->getBasics(),
'work' => $this->resume->getWork(),
'volunteer' => $this->resume->getVolunteer(),
'education' => $this->resume->getEducation(),
'awards' => $this->resume->getAwards(),
'publications' => $this->resume->getPublications(),
'skills' => $this->resume->getSkills(),
'languages' => $this->resume->getLanguages(),
'interests' => $this->resume->getInterests(),
'references' => $this->resume->getReferences(),
]);
}
public function setHost($host)
{
$this->host = $host;
}
public function getHost()
{
return $this->host;
}
/**
* @return string
*/
public function getCover()
{
$host = $this->getHost() ?: $this->request->getSchemeAndHttpHost();
return $this->app->renderView('cover.html.twig', [
'baseUrl' => $host,
'renderPdf' => true,
'basics' => $this->resume->getBasics(),
]);
}
/**
* @return string
*/
public function getTitle()
{
$date = new \DateTimeImmutable('now');
$date = $date->format('c');
$title = $this->app->config('app.title') ?: 'Resume';
return sprintf('%s (%s)', $title, $date);
}
}

View File

@ -9,42 +9,283 @@
* file that was distributed with this source code. * file that was distributed with this source code.
*/ */
/**
* Created by PhpStorm.
* User: eric
* Date: 7/2/16
* Time: 11:22 AM
*/
namespace Sikofitt\Resume; namespace Sikofitt\Resume;
use Webmozart\Json\Conversion\JsonConverter; use App;
/** /**
* Class ResumeParser * Class ResumeParser
*
* @package Sikofitt\Resume * @package Sikofitt\Resume
*/ */
class ResumeParser implements JsonConverter class ResumeParser extends \ArrayObject implements \JsonSerializable, \ArrayAccess
{ {
/** /**
* @param mixed $jsonData * @var
* @param array $options
*
* @return void
*/ */
public function fromJson($jsonData, array $options = []) private $input;
/**
* @var
*/
private $basics;
/**
* @var
*/
private $work;
/**
* @var
*/
private $volunteer;
/**
* @var
*/
private $education;
/**
* @var
*/
private $awards;
/**
* @var
*/
private $publications;
/**
* @var
*/
private $skills;
/**
* @var
*/
private $languages;
/**
* @var
*/
private $interests;
/**
* @var
*/
private $references;
/**
* ResumeParser constructor.
*
* @param App $app
* @param int $flags
* @param string $iterator_class
*/
public function __construct(App $app, $flags = 0, $iterator_class = 'ArrayIterator')
{ {
// TODO: Implement fromJson() method. $this->app = $app;
$this->input = $this->app->decodeFile($app->getResumeJson(), $app->getResumeSchema());
parent::__construct($this->input, $flags, $iterator_class);
$this
->setBasics($this->offsetGet('basics'))
->setWork($this->offsetGet('work'))
->setVolunteer($this->offsetGet('volunteer'))
->setEducation($this->offsetGet('education'))
->setAwards($this->offsetGet('awards'))
->setPublications($this->offsetGet('publications'))
->setSkills($this->offsetGet('skills'))
->setLanguages($this->offsetGet('languages'))
->setInterests($this->offsetGet('interests'))
->setReferences($this->offsetGet('references'));
} }
/** /**
* @param mixed $data * @return mixed
* @param array $options
*
* @return void
*/ */
public function toJson($data, array $options = []) public function jsonSerialize()
{ {
// TODO: Implement toJson() method. return $this->app->encode($this->input, $this->app->getResumeSchema());
}
/**
* @return mixed
*/
public function getBasics()
{
return $this->basics;
}
/**
* @param mixed $basics
*
* @return ResumeParser
*/
public function setBasics($basics)
{
$this->basics = $basics;
return $this;
}
/**
* @return mixed
*/
public function getWork()
{
return $this->work;
}
/**
* @param mixed $work
*
* @return ResumeParser
*/
public function setWork($work)
{
$this->work = $work;
return $this;
}
/**
* @return mixed
*/
public function getVolunteer()
{
return $this->volunteer;
}
/**
* @param mixed $volunteer
*
* @return ResumeParser
*/
public function setVolunteer($volunteer)
{
$this->volunteer = $volunteer;
return $this;
}
/**
* @return mixed
*/
public function getEducation()
{
return $this->education;
}
/**
* @param mixed $education
*
* @return ResumeParser
*/
public function setEducation($education)
{
$this->education = $education;
return $this;
}
/**
* @return mixed
*/
public function getAwards()
{
return $this->awards;
}
/**
* @param mixed $awards
*
* @return ResumeParser
*/
public function setAwards($awards)
{
$this->awards = $awards;
return $this;
}
/**
* @return mixed
*/
public function getPublications()
{
return $this->publications;
}
/**
* @param mixed $publications
*
* @return ResumeParser
*/
public function setPublications($publications)
{
$this->publications = $publications;
return $this;
}
/**
* @return mixed
*/
public function getSkills()
{
return $this->skills;
}
/**
* @param mixed $skills
*
* @return ResumeParser
*/
public function setSkills($skills)
{
$this->skills = $skills;
return $this;
}
/**
* @return mixed
*/
public function getLanguages()
{
return $this->languages;
}
/**
* @param mixed $languages
*
* @return ResumeParser
*/
public function setLanguages($languages)
{
$this->languages = $languages;
return $this;
}
/**
* @return mixed
*/
public function getInterests()
{
return $this->interests;
}
/**
* @param mixed $interests
*
* @return ResumeParser
*/
public function setInterests($interests)
{
$this->interests = $interests;
return $this;
}
/**
* @return mixed
*/
public function getReferences()
{
return $this->references;
}
/**
* @param mixed $references
*
* @return ResumeParser
*/
public function setReferences($references)
{
$this->references = $references;
return $this;
} }
} }

View File

@ -39,7 +39,7 @@ class RenderProfile extends \Twig_Extension
]; ];
} }
public function renderProfile($context, $iconData) public function renderProfile($context, $iconData, $withText = false)
{ {
$imageData = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAAZCAIAAABLixI0AAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4AcHEQYsjAFXqQAAAEVpVFh0Q29tbWVudAAAAAAAQ1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgSlBFRyB2ODApLCBxdWFsaXR5ID0gOTAKqozFDgAAAiVJREFUOMutlT/oOWEcx+8OX5fLDVwx2ZQFSQYzmZgMzCaDzaCUMpmQkowM2G+gFFnUGXRKSQwGIYtyisif+w2n+973ucef4fde7u75fD6v5/l8Ps/zHMLDxHFcp9OJxWJOp1Ov1ysUChzHLRZLNBrt9Xrn8xkahciH6vW6VqtFXouiqOFw+JmVz+eR79Rqtd6xSqUS8rUwDGNZFs7a7/c4jku9lUplOByuVqvtdpum6Xg8jqKo1MHn88FZHo8HmPnn50co8+PxeLVwjuNA1mq1giaSTCaDwaDX653P58+Av6pUKiJLKQxNJhMUReWumUxGeGFZ1mw2yydbLpe/FRQeh8NBDhJFkmQgEEAQJJ1OAyaDwfD7ISyv2+0CdZV2YL1e8zw/Go3UajXQSkjtj8fjK1YkEnmWQ6kETEajUcp65qjRaAqFApRls9kQBBkMBrfbDTBtt9tGowHmKMhqtUJzJAhCpVJBZ9LpdKfTCbLvGYaRe2ez2d1uV6vV5CahLAzDwM9jIpEAAjabDXRnCSqXy5fLBc46HA4kSUq9c7kcz/PQdQElAlnX69XhcHx5vP1+P6SP0i0DnPD3rD+xgPnxeJhMpm9AKIra7faXCYtaLBYURUHjRWEYJnbw5R0t6HK5hEIhKYggiGazOZ1O+/1+u90ej8ef73up3G63yEqlUu+dP7Du97vL5RL+F/wnIR89ZrMZhmHFYvE/sHiep2lavFff6B8xFGrMmf/uPQAAAABJRU5ErkJggg=='; $imageData = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABkAAAAZCAIAAABLixI0AAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH4AcHEQYsjAFXqQAAAEVpVFh0Q29tbWVudAAAAAAAQ1JFQVRPUjogZ2QtanBlZyB2MS4wICh1c2luZyBJSkcgSlBFRyB2ODApLCBxdWFsaXR5ID0gOTAKqozFDgAAAiVJREFUOMutlT/oOWEcx+8OX5fLDVwx2ZQFSQYzmZgMzCaDzaCUMpmQkowM2G+gFFnUGXRKSQwGIYtyisif+w2n+973ucef4fde7u75fD6v5/l8Ps/zHMLDxHFcp9OJxWJOp1Ov1ysUChzHLRZLNBrt9Xrn8xkahciH6vW6VqtFXouiqOFw+JmVz+eR79Rqtd6xSqUS8rUwDGNZFs7a7/c4jku9lUplOByuVqvtdpum6Xg8jqKo1MHn88FZHo8HmPnn50co8+PxeLVwjuNA1mq1giaSTCaDwaDX653P58+Av6pUKiJLKQxNJhMUReWumUxGeGFZ1mw2yydbLpe/FRQeh8NBDhJFkmQgEEAQJJ1OAyaDwfD7ISyv2+0CdZV2YL1e8zw/Go3UajXQSkjtj8fjK1YkEnmWQ6kETEajUcp65qjRaAqFApRls9kQBBkMBrfbDTBtt9tGowHmKMhqtUJzJAhCpVJBZ9LpdKfTCbLvGYaRe2ez2d1uV6vV5CahLAzDwM9jIpEAAjabDXRnCSqXy5fLBc46HA4kSUq9c7kcz/PQdQElAlnX69XhcHx5vP1+P6SP0i0DnPD3rD+xgPnxeJhMpm9AKIra7faXCYtaLBYURUHjRWEYJnbw5R0t6HK5hEIhKYggiGazOZ1O+/1+u90ej8ef73up3G63yEqlUu+dP7Du97vL5RL+F/wnIR89ZrMZhmHFYvE/sHiep2lavFff6B8xFGrMmf/uPQAAAABJRU5ErkJggg==';
//network": "Twitter" +"username": "sikofitt" +"url": "" //network": "Twitter" +"username": "sikofitt" +"url": ""
@ -57,7 +57,11 @@ class RenderProfile extends \Twig_Extension
} }
$imageUrl = sprintf('<img src="%s" alt="%s" />', $imageData['icon'], $iconData->network); $imageUrl = sprintf('<img src="%s" alt="%s" />', $imageData['icon'], $iconData->network);
if (isset($iconData->url) && !empty($iconData->url)) { if (isset($iconData->url) && !empty($iconData->url)) {
if ($withText) {
return sprintf('<a class="profile-link" href="%s" title="%s" target="_blank"><span class="uk-margin-small-right">%s</span> %s</a>', $iconData->url, $iconData->network, $imageUrl, $iconData->url);
} else {
return sprintf('<a class="profile-link" href="%s" title="%s" target="_blank">%s</a>', $iconData->url, $iconData->network, $imageUrl); return sprintf('<a class="profile-link" href="%s" title="%s" target="_blank">%s</a>', $iconData->url, $iconData->network, $imageUrl);
}
} else { } else {
return $imageUrl; return $imageUrl;
} }

View File

@ -77,4 +77,6 @@ jq(document).ready(function (jq) {
jq(this).removeClass('uk-form-danger'); jq(this).removeClass('uk-form-danger');
} }
}); });
jq('a[rel=ext]').attr('target', '_blank');
}); });

View File

@ -28,6 +28,10 @@ a.profile-link {
&:hover { &:hover {
filter: grayscale(0%); filter: grayscale(0%);
} }
> * img {
width:25px;
padding:0px;
}
} }
.uk-panel-image { .uk-panel-image {
@ -41,6 +45,7 @@ a.profile-link {
} }
.uk-text-lead { .uk-text-lead {
font-size: 24px; font-size: 24px;
line-height: 38px; line-height: 38px;
@ -63,3 +68,12 @@ a.profile-link {
font-size: 34px; font-size: 34px;
border-radius: 2px; border-radius: 2px;
} }
.uk-sticky-active-sidebar {
margin-top: 35px;
}
div#sidebar.uk-active {
margin-top: 35px;
transition: margin-top 2s;
}

9
web/.htaccess Normal file
View File

@ -0,0 +1,9 @@
<IfModule mod_rewrite.c>
Options -MultiViews
RewriteEngine On
#RewriteBase /path/to/app
RewriteCond %{REQUEST_FILENAME} !-d
RewriteCond %{REQUEST_FILENAME} !-f
RewriteRule ^ index.php [QSA,L]
</IfModule>

View File

@ -9,7 +9,6 @@
* file that was distributed with this source code. * file that was distributed with this source code.
*/ */
require_once __DIR__ . '/../vendor/autoload.php'; require_once __DIR__ . '/../vendor/autoload.php';

5
web/robots.txt Normal file
View File

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