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,75 +32,72 @@ class App extends Application
use Application\UrlGeneratorTrait; use Application\UrlGeneratorTrait;
private $debug; private $debug;
/**
* Returns the application directory.
*
* @return string
* The main application directory.
*/
public function getAppDirectory()
{
$r = new ReflectionClass($this);
return dirname($r->getFileName());
}
/** /**
* Returns the root directory of the application. * @return string
* */
* @return string public function getConfDirectory()
* The root directory of the application. {
*/ return $this->getAppDirectory() . '/config';
public function getRootDirectory() }
{
return dirname($this->getAppDirectory());
}
/** /**
* @return string * Returns the application directory.
*/ *
public function getConfDirectory() * @return string
{ * The main application directory.
return $this->getAppDirectory() . '/config'; */
} public function getAppDirectory()
{
$r = new ReflectionClass($this);
return dirname($r->getFileName());
}
/** /**
* @return string * @return string
*/ */
public function getDataDirectory() public function getResumeJson()
{ {
return $this->getRootDirectory() . '/data'; return $this->getDataDirectory() . '/resume.json';
} }
/** /**
* @return string * @return string
*/ */
public function getResumeJson() public function getDataDirectory()
{ {
return $this->getDataDirectory() . '/resume.json'; return $this->getRootDirectory() . '/data';
} }
/** /**
* @return string * Returns the root directory of the application.
*/ *
public function getResumeSchema() * @return string
{ * The root directory of the application.
return $this->getDataDirectory() . '/schema/schema.v1.json'; */
} public function getRootDirectory()
{
return dirname($this->getAppDirectory());
}
/**
* @return string
*/
public function getResumeSchema()
{
return $this->getDataDirectory() . '/schema/schema.v1.json';
}
public function getLogDirectory() public function getLogDirectory()
{ {
return $this->getDataDirectory() . '/logs'; return $this->getDataDirectory() . '/logs';
} }
/** public function getDebug()
* Registers media icons {
* return $this['debug'];
* @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()]);
}
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,77 +107,80 @@ class App extends Application
} }
} }
public function getDebug()
{
return $this['debug'];
}
public function registerExtenders()
{
if (!$this['debug']) {
$this->log('In Error handler.');
$this->error(function (\Exception $e, \Symfony\Component\HttpFoundation\Request $request, $code) {
switch ($code) {
case 405:
preg_match('/\(Allow\:(.+)\)/', $e->getMessage(), $matches);
if (isset($matches[1])) {
$matches = trim($matches[1]);
} elseif (isset($matches[0])) {
$matches = trim($matches[0]);
} else {
$matches = 'Available methods are unknown.';
}
$message = [
'status' => 'error',
'message' => 'Method not allowed',
'allowedMethods' => $matches,
'requestedMethod' => $request->getMethod(),
'code' => $code,
];
if($request->isXmlHttpRequest()) {
$message = json_encode($message);
} else {
$message = $this->renderView('error.405.html.twig', $message);
}
$this->log($e->getMessage(), ['code' => $code], \Symfony\Bridge\Monolog\Logger::WARNING);
break;
case 500:
$message = json_encode(['status' => 'error', 'message' => 'Critical Error', 'code' => $code]);
$this->log($e->getMessage(), ['code' => $code], \Symfony\Bridge\Monolog\Logger::CRITICAL);
break;
default:
$message = ['status' => 'error', 'message' => $e->getMessage(), 'code' => $code, 'requestUri' => $request->getRequestUri()];
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::ERROR);
break;
}
return new \Symfony\Component\HttpFoundation\Response($message, $code);
});
}
$this->extend('twig', function (\Twig_Environment $twig) {
if ($this['debug']) {
$twig->enableDebug();
}
$twig->addExtension(new Twig_Extensions_Extension_Date());
$twig->addExtension(new Sikofitt\Twig\Date());
$twig->addExtension(new Sikofitt\Twig\RenderProfile());
$twig->addGlobal('config', $this->config('all'));
return $twig;
});
}
public function boot() public function boot()
{ {
$this->registerExtenders(); $this->registerExtenders();
// register default icons // register default icons
$this->registerDefaultIcons(); $this->registerDefaultIcons();
return parent::boot(); return parent::boot();
} }
public function registerExtenders()
{
if (!$this['debug']) {
$this->log('In Error handler.');
$this->error(function (\Exception $e, \Symfony\Component\HttpFoundation\Request $request, $code) {
switch ($code) {
case 405:
preg_match('/\(Allow\:(.+)\)/', $e->getMessage(), $matches);
if (isset($matches[1])) {
$matches = trim($matches[1]);
} elseif (isset($matches[0])) {
$matches = trim($matches[0]);
} else {
$matches = 'Available methods are unknown.';
}
$message = [
'status' => 'error',
'message' => 'Method not allowed',
'allowedMethods' => $matches,
'requestedMethod' => $request->getMethod(),
'code' => $code,
];
if ($request->isXmlHttpRequest()) {
$message = json_encode($message);
} else {
$message = $this->renderView('error.405.html.twig', $message);
}
$this->log($e->getMessage(), ['code' => $code], \Symfony\Bridge\Monolog\Logger::WARNING);
break;
case 500:
$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);
break;
default:
$message = ['status' => 'error', 'message' => $e->getMessage(), 'code' => $code, 'requestUri' => $request->getRequestUri()];
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::ERROR);
break;
}
return new \Symfony\Component\HttpFoundation\Response($message, $code);
});
}
$this->extend('twig', function (\Twig_Environment $twig) {
if ($this['debug']) {
$twig->enableDebug();
}
$twig->addExtension(new Twig_Extensions_Extension_Date());
$twig->addExtension(new Sikofitt\Twig\Date());
$twig->addExtension(new Sikofitt\Twig\RenderProfile());
$twig->addGlobal('config', $this->config('all'));
return $twig;
});
}
public function registerDefaultIcons() public function registerDefaultIcons()
{ {
$this->registerIcon(new \Sikofitt\Image\Profile\TwitterProfileIcon()); $this->registerIcon(new \Sikofitt\Image\Profile\TwitterProfileIcon());
@ -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,25 +73,25 @@ $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'),
'username' => $app->config('app.smtp_user'), 'username' => $app->config('app.smtp_user'),
'password' => $app->config('app.smtp_password'), 'password' => $app->config('app.smtp_password'),
]; ];
$app->log('Setting up local email.'); $app->log('Setting up local email.');
} elseif (false !== getenv('SPARKPOST_API_KEY')) { } elseif (false !== getenv('SPARKPOST_API_KEY')) {
$app['swiftmailer.options'] = [ $app['swiftmailer.options'] = [
'host' => getenv('SPARKPOST_SMTP_HOST'), 'host' => getenv('SPARKPOST_SMTP_HOST'),
'port' => getenv('SPARKPOST_SMTP_PORT'), 'port' => getenv('SPARKPOST_SMTP_PORT'),
'username' => getenv('SPARKPOST_SMTP_USERNAME'), 'username' => getenv('SPARKPOST_SMTP_USERNAME'),
'password' => getenv('SPARKPOST_SMTP_PASSWORD'), 'password' => getenv('SPARKPOST_SMTP_PASSWORD'),
'encryption' => 'tls', 'encryption' => 'tls',
]; ];
$app->log('Setting up sparkpost email.'); $app->log('Setting up sparkpost email.');
} else { } else {
$app['swiftmailer.transport'] = new Swift_SendmailTransport(); $app['swiftmailer.transport'] = new Swift_SendmailTransport();
} }
$app->register(new RoutingServiceProvider()) $app->register(new RoutingServiceProvider())

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,34 +5,28 @@
{% 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">
<div class="uk-width-1-1"> {% if renderPdf == false %}
<h1 class="uk-heading-large"> <div class="uk-width-1-1">
{% if basics.name is not empty %}
{{ basics.name|title }}
{% else %}
{{ app.config.app.title|default('Resume') }}
{% endif %}
{% if basics.label is not empty %}
<small class="uk-h2 uk-align-right uk-text-bottom">{{ basics.label }}</small>
{% endif %}
</h1>
<hr/>
{% if basics.summary is not empty %}
<p class="uk-text-lead">{{ basics.summary|raw }}</p>
{% endif %}
</div> <h1 class="uk-heading-large">
{% if basics.name is not empty %}
{{ basics.name|title }}
{% else %}
{{ app.config.app.title|default('Resume') }}
{% endif %}
{% if basics.label is not empty %}
<small class="uk-h2 uk-align-right uk-text-bottom">{{ basics.label }}</small>
{% endif %}
</h1>
<hr/>
{% if basics.summary is not empty %}
<p class="uk-text-lead">{{ basics.summary|raw }}</p>
{% endif %}
</div>
{% endif %}
</div> </div>
<div class="uk-grid" data-uk-grid-margin> <div class="uk-grid" data-uk-grid-margin>
@ -41,69 +35,81 @@
{% include 'work.html.twig' %} {% include 'work.html.twig' %}
{% include 'references.html.twig' %} {% include 'references.html.twig' %}
</div> </div>
<div class="uk-width-medium-1-4"> {% if renderPdf == false %}
<div id="sidebar" class="uk-panel uk-panel-header uk-panel-box" <div class="uk-width-medium-1-4">
data-uk-sticky="{top:35, animation: 'uk-animation-slide-top', getWidthFrom:'#sidebar'}"> <div id="sidebar" class="uk-panel uk-panel-header uk-panel-box"
<div class="uk-panel-image"> data-uk-sticky="{top:35, animation: 'uk-animation-slide-top', getWidthFrom:'#sidebar'}">
<img <div class="uk-panel-image">
class="uk-align-right uk-thumbnail uk-border-circle uk-thumbnail-mini uk-animation-scale-up uk-img-preserve" <img
src="{{ basics.picture }}"/> class="uk-align-right uk-thumbnail uk-border-circle uk-thumbnail-mini uk-animation-scale-up uk-img-preserve"
</div> src="{{ basics.picture }}"/>
<h3 class="uk-panel-title">Contact</h3> </div>
<h3 class="uk-panel-title">Contact</h3>
<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 %}
<li class="uk-list-space"><a href="#phone-modal" class="hidden-phone" data-uk-modal>Phone</a></li> {% if renderPdf == true %}
{% endif %} <li class="uk-list-space"><a href="tel:{{ app.config.app.phone }}"
title="Telephone">{{ app.config.app.phone }}</a></li>
<li class="uk-list-space"> {% else %}
{% if basics.email is not empty %} <li class="uk-list-space"><a href="#phone-modal" class="hidden-phone" data-uk-modal>Phone</a></li>
<a href="#contact-form-wrapper" data-uk-modal>{{ basics.email }}</a> {% endif %}
{% endif %} {% endif %}
</li>
{% if basics.website is not empty %}
<li class="uk-list-space"> <li class="uk-list-space">
<a href="{{ basics.website }}" target="_blank" title="Home page">{{ basics.website }}</a> {% 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>
{% endif %}
{% endif %}
</li> </li>
{% endif %}
{% if basics.location|length > 0 and basics.location is not empty %} {% if basics.website is not empty %}
<li class="uk-list-space">
<a href="{{ basics.website }}" target="_blank" title="Home page">{{ basics.website }}</a>
</li>
{% 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 %}
<li class="uk-list-space">
<address>
{% set location = basics.location %}
{% if location.address is not empty %}
{{ location.address }}<br/>
{% endif %}
{% if location.city is not empty %}
{{ location.city }},
{% endif %}
{% if location.region is not empty %}
&nbsp;{{ location.region }},
{% endif %}
{% if location.countryCode is not empty %}
&nbsp;{{ location.countryCode }}
{% endif %}<br/>
</address>
</li>
{% endif %}
<li class="uk-list-space"> <li class="uk-list-space">
<address> {% for profile in basics.profiles %}
{% set location = basics.location %} {{ render_profile(profile)|raw }}
{% endfor %}
{% if location.address is not empty %}
{{ location.address }}<br/>
{% endif %}
{% if location.city is not empty %}
{{ location.city }},
{% endif %}
{% if location.region is not empty %}
&nbsp;{{ location.region }},
{% endif %}
{% if location.countryCode is not empty %}
&nbsp;{{ location.countryCode }}
{% endif %}<br/>
</address>
</li> </li>
{% endif %}
<li class="uk-list-space">
{% for profile in basics.profiles %}
{{ render_profile(profile)|raw }}
{% endfor %}
</li> </ul>
</div>
</ul>
</div> </div>
{% endif %}
</div>
</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 %}
</div>
{% endif %} {% endif %}
</div>

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,9 +37,9 @@ 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.')
->setProcessTitle('PDF Creation'); ->setProcessTitle('PDF Creation');
} }
@ -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,27 +30,30 @@ 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']);
return $config; return $config;
}; };
} }
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'),
]; ];
$constraints = [ $constraints = [
'email' => [ 'email' => [
new NotNull(['message' => 'Email value in app config is not present.']), new NotNull(['message' => 'Email value in app config is not present.']),
new NotBlank(['message' => 'Email should cannot be blank in config.']), new NotBlank(['message' => 'Email should cannot be blank in config.']),
@ -66,30 +69,29 @@ class ConfigServiceProvider implements ServiceProviderInterface, BootableProvide
], ],
]; ];
$captcha = $app->config('app.captcha'); $captcha = $app->config('app.captcha');
if (isset($captcha) && $captcha) { if (isset($captcha) && $captcha) {
$configItems['captcha_sitekey'] = $app->config('app.captcha_sitekey'); $configItems['captcha_sitekey'] = $app->config('app.captcha_sitekey');
$configItems['captcha_secret'] = $app->config('app.captcha_secret'); $configItems['captcha_secret'] = $app->config('app.captcha_secret');
$constraints['captcha_sitekey'] = [ $constraints['captcha_sitekey'] = [
new NotNull(['message' => 'ReCaptcha sitekey is a required value to use the captcha, this check can be disabled by removing or setting the captcha config item to false.']), new NotNull(['message' => 'ReCaptcha sitekey is a required value to use the captcha, this check can be disabled by removing or setting the captcha config item to false.']),
new NotBlank(), new NotBlank(),
new Length(['min' => 40]), new Length(['min' => 40]),
]; ];
$constraints['captcha_secret'] = [ $constraints['captcha_secret'] = [
new NotNull(), new NotNull(),
new NotBlank(), new NotBlank(),
new Length(['min' => 40]), new Length(['min' => 40]),
]; ];
}
$errors = $app['validator']->validate($configItems, new Collection($constraints));
$validationErrors = [];
foreach ($errors->getIterator() as $error) {
$validationErrors[] = $error->getMessage();
}
if (count($validationErrors) > 0) {
throw new MissingConfigurationItemException($validationErrors[0]);
}
} }
$errors = $app['validator']->validate($configItems, new Collection($constraints));
$validationErrors = [];
foreach ($errors->getIterator() as $error) {
$validationErrors[] = $error->getMessage();
}
if (count($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,19 +43,21 @@ use Symfony\Component\Validator\Constraints\Type;
* *
* @package Sikofitt\Controller * @package Sikofitt\Controller
*/ */
class ApiControllerProvider implements ControllerProviderInterface { class ApiControllerProvider implements ControllerProviderInterface
{
/** /**
* {@inheritdoc} * {@inheritdoc}
* *
* @param Application $app * @param Application $app
* *
* @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) {
$response = new Response(file_get_contents($app->getDataDirectory() . '/schema/schema.v1.json'), Response::HTTP_OK); $response = new Response(file_get_contents($app->getDataDirectory() . '/schema/schema.v1.json'), Response::HTTP_OK);
$response->headers->set('Content-Type', 'application/schema+json'); $response->headers->set('Content-Type', 'application/schema+json');
@ -64,7 +65,7 @@ class ApiControllerProvider implements ControllerProviderInterface {
return $response; return $response;
}); });
$controllers->match('/v1/message', function (Request $request) use ($app) { $controllers->match('/v1/message', function (Request $request) use ($app) {
static $code = 255; static $code = 255;
$returnData = [ $returnData = [
@ -121,7 +122,7 @@ class ApiControllerProvider implements ControllerProviderInterface {
$valid = $app['validator']->validate($contactFormData, new Collection($constraints)); $valid = $app['validator']->validate($contactFormData, new Collection($constraints));
if (count($valid) > 0) { if (count($valid) > 0) {
$sanitizeProperty = function () use ($valid) { $sanitizeProperty = function () use ($valid) {
return str_replace(['][', '[', ']'], [ return str_replace(['][', '[', ']'], [
'_', '_',
'', '',
@ -129,59 +130,55 @@ class ApiControllerProvider implements ControllerProviderInterface {
], $valid[0]->getPropertyPath()); ], $valid[0]->getPropertyPath());
}; };
return new JsonResponse([ return new JsonResponse([
'status' => 'error', 'status' => 'error',
'message' => $valid[0]->getMessage(), 'message' => $valid[0]->getMessage(),
'id' => $sanitizeProperty(), 'id' => $sanitizeProperty(),
'const' => $valid[0]->getCode(), 'const' => $valid[0]->getCode(),
'code' => 256, 'code' => 256,
], 256); ], 256);
} else {
} $contactFormName = $contactFormData['contact']['name'];
else { $contactFormEmail = $contactFormData['contact']['email'];
$contactFormMessage = sprintf("%s\n\nEmail : %s <%s>", $contactFormData['contact']['message'], $contactFormName, $contactFormEmail);
$contactFormName = $contactFormData['contact']['name']; $failures = '';
$contactFormEmail = $contactFormData['contact']['email']; $message = \Swift_Message::newInstance()
$contactFormMessage = sprintf("%s\n\nEmail : %s <%s>", $contactFormData['contact']['message'], $contactFormName, $contactFormEmail);
$failures = '';
$message = \Swift_Message::newInstance()
->setSubject('[Resume] Message') ->setSubject('[Resume] Message')
->setFrom([$app->config('app.from_email') => $contactFormName]) ->setFrom([$app->config('app.from_email') => $contactFormName])
->setTo([$app->config('app.email') => 'No-Reply']) ->setTo([$app->config('app.email') => 'No-Reply'])
->setReplyTo([$contactFormEmail => $contactFormName]) ->setReplyTo([$contactFormEmail => $contactFormName])
->setBody($contactFormMessage); ->setBody($contactFormMessage);
$sent = $app['mailer']->send($message, $failures); $sent = $app['mailer']->send($message, $failures);
if ($sent > 0) { if ($sent > 0) {
$request->getSession()->remove('_csrf/contact'); $request->getSession()->remove('_csrf/contact');
return new JsonResponse([ return new JsonResponse([
'status' => 'success', 'status' => 'success',
'message' => 'Message successfully sent.', 'message' => 'Message successfully sent.',
'code' => 201, 'code' => 201,
'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.',
'code' => 255, 'code' => 255,
'failed' => $failures, 'failed' => $failures,
'sent' => $sent, 'sent' => $sent,
], 255); ], 255);
} }
} }
})->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');
$controllers->post('/v1/captcha', function (Request $request) use ($app) { $controllers->post('/v1/captcha', function (Request $request) use ($app) {
$captcha = new ReCaptcha('6LcvmSQTAAAAAITkvYJjgLar1LqGGLz-ic0ZMiXo'); $captcha = new ReCaptcha('6LcvmSQTAAAAAITkvYJjgLar1LqGGLz-ic0ZMiXo');
$valid = $captcha->verify( $valid = $captcha->verify(
@ -189,31 +186,30 @@ class ApiControllerProvider implements ControllerProviderInterface {
$request->server->get('REMOTE_ADDR') $request->server->get('REMOTE_ADDR')
); );
if ($valid->isSuccess()) { if ($valid->isSuccess()) {
$return = [ $return = [
'valid' => true, 'valid' => true,
'message' => [ 'message' => [
'email' => null !== $app->config('app.email') ? $app->config('app.email') : 'No email has been set in the configuration. Please let the owner know.', 'email' => null !== $app->config('app.email') ? $app->config('app.email') : 'No email 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.', '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.',
'missing-input-response' => 'The response parameter is missing.', 'missing-input-response' => 'The response parameter is missing.',
'invalid-input-response' => 'The response parameter is invalid or malformed.', 'invalid-input-response' => 'The response parameter is invalid or malformed.',
]; ];
foreach ($valid->getErrorCodes() as $code) { foreach ($valid->getErrorCodes() as $code) {
if (array_key_exists($code, $errorCodes)) { if (array_key_exists($code, $errorCodes)) {
$errors[] = $errorCodes[$code]; $errors[] = $errorCodes[$code];
}
} }
} if (!isset($errors)) {
if (!isset($errors)) { $errors[] = 'An unknown error occurred.';
$errors[] = 'An unknown error occurred.'; }
} $return = [
$return = [
'valid' => false, 'valid' => false,
'message' => $errors, 'message' => $errors,
]; ];
@ -222,6 +218,6 @@ class ApiControllerProvider implements ControllerProviderInterface {
return new JsonResponse(json_encode($return)); return new JsonResponse(json_encode($return));
})->bind('api_captcha'); })->bind('api_captcha');
return $controllers; return $controllers;
} }
} }

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( {
$app->getDataDirectory() . '/resume.json', $this->resume = new ResumeParser($app);
$app->getDataDirectory() . '/schema/schema.v1.json'
);
$contactForm = $app['form.factory']->create(ContactType::class); $controllers = $app['controllers_factory'];
$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()); $controllers->get('/', function (Request $request) use ($app) {
$response = new Response(); $contactForm = $app['form.factory']->create(ContactType::class);
$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'];
$contactFormEmail = $contactFormData['contact']['email'];
$contactFormMessage = $contactFormData['contact']['message'];
$sent = $app['mailer']->send(\Swift_Message::newInstance()
->setSubject('[Resume] Message')
->setFrom([$contactFormEmail => $contactFormName])
->setTo($app->config('app.email'))
->setBody($contactFormMessage)
, $failures);
dump($failures);
dump($sent);
return new JsonResponse(['failures' => $failures, 'sent' => (bool)$sent]);
});
return $controllers; $pdf = new Pdf($app, $request);
}
/** $renderedPdf = $pdf->render();
* @return null
*/
public function getResumeBasics()
{
return (isset($this->resumeData->basics) && count($this->resumeData->basics) > 0) ? $this->resumeData->basics : null;
}
/** $filename = str_replace('@', '-AT-', $this->resume->getBasics()->email);
* @return null
*/
public function getResumeWork()
{
return (isset($this->resumeData->work) && count($this->resumeData->work) > 0) ? $this->resumeData->work : null;
}
/** $response = new StreamedResponse();
* @return null $response->setCallback(function () use ($renderedPdf) {
*/ print $renderedPdf;
public function getResumeVolunteer() });
{
return (isset($this->resumeData->volunteer) && count($this->resumeData->volunteer) > 0) ? $this->resumeData->volunteer : null;
}
/** $response->setStatusCode(Response::HTTP_OK);
* @return null $disposition = $response->headers->makeDisposition(
*/ ResponseHeaderBag::DISPOSITION_ATTACHMENT,
public function getResumeEducation() $filename . '.pdf'
{ );
return (isset($this->resumeData->education) && count($this->resumeData->education) > 0) ? $this->resumeData->education : null; $response->headers->set('Content-Disposition', $disposition);
} $response->send();
});
/**
* @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 $controllers;
* @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

@ -32,13 +32,12 @@ use Symfony\Component\Validator\Constraints\NotBlank;
class ContactType extends AbstractType class ContactType extends AbstractType
{ {
/** /**
* @param FormBuilderInterface $builder * @param FormBuilderInterface $builder
* @param array $options * @param array $options
*/ */
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)) {
return sprintf('<a class="profile-link" href="%s" title="%s" target="_blank">%s</a>', $iconData->url, $iconData->network, $imageUrl); 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);
}
} 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;
@ -62,4 +67,13 @@ a.profile-link {
line-height: 40px; line-height: 40px;
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: