diff --git a/Gruntfile.js b/Gruntfile.js index d3fc1b0..29b1f6e 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -74,9 +74,9 @@ module.exports = function (grunt) { mangle: true, report: 'gzip', compress: { - drop_console: true + drop_console: false }, - banner: '/*! \n * Resume.PHP - v<%= pkg.version %> @license MIT (http://resume.reric.me)\n' + + banner: '/*! \n * Resume.PHP - v<%= pkg.version %> MIT (http://resume.reric.me)\n' + ' * <%= grunt.template.today("dddd, mmmm dS, yyyy, h:MM:ss tt") %> \n */\n', footer: '\n/*! Resume.PHP end */', nameCache: '.tmp/grunt-uglify-cache.json', diff --git a/app/config/config.yml b/app/config/config.yml index 40a5313..9ace595 100644 --- a/app/config/config.yml +++ b/app/config/config.yml @@ -9,8 +9,4 @@ app: captcha: true captcha_sitekey: 6LcvmSQTAAAAAMmf9w6mhCbpdLvknuD9SGVHT0q- captcha_secret: 6LcvmSQTAAAAAITkvYJjgLar1LqGGLz-ic0ZMiXo - twig_theme: default - twig_paths: - - views - - default - twig_template: uikit.html.twig + theme: default diff --git a/app/config/config.yml.dist b/app/config/config.yml.dist index a06a44c..648a3c3 100644 --- a/app/config/config.yml.dist +++ b/app/config/config.yml.dist @@ -8,8 +8,8 @@ app: captcha: true captcha_sitekey: 6LcvmSQTAAAAAMmf9w6mhCbpdLvknuD9SGVHT0q- captcha_secret: 6LcvmSQTAAAAAITkvYJjgLar1LqGGLz-ic0ZMiXo - twig_theme: default - twig_paths: - - views - twig_template: uikit.html.twig + theme: default + #twig_paths: + # - views + #twig_template: uikit.html.twig diff --git a/app/config/default/config.php b/app/config/default/config.php deleted file mode 100644 index e0e9429..0000000 --- a/app/config/default/config.php +++ /dev/null @@ -1,14 +0,0 @@ - - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -return [ - 'app' => 'neat', -]; diff --git a/app/providers.php b/app/providers.php index 8ee1520..34d365f 100644 --- a/app/providers.php +++ b/app/providers.php @@ -38,11 +38,10 @@ $app->register(new ConfigServiceProvider(), [ $app->setDebug(); + $app ->register(new TwigServiceProvider(), [ - 'twig.path' => [ - $app->getRootDirectory() . '/app/views', - ], + 'twig.path' => $app->getAppDirectory() . '/themes/' . $app->config('app.theme'), ]) ->register(new JsonServiceProvider()) ->register(new AssetServiceProvider()) @@ -59,7 +58,22 @@ $app 'monolog.name' => 'Resume.PHP', 'monolog.level' => $app->getDebug() ? Logger::DEBUG : Logger::INFO, ]) - ->register(new RoutingServiceProvider()) + ->register(new \Silex\Provider\SwiftmailerServiceProvider()); + + if(false === getenv('SPARKPOST_API_KEY')) { + $app['swiftmailer.transport'] = new Swift_SendmailTransport(); + } else { + $app['swiftmailer.options'] = [ + 'host' => getenv('SPARKPOST_SMTP_HOST'), + 'port' => getenv('SPARKPOST_SMTP_PORT'), + 'username' => getenv('SPARKPOST_SMTP_USERNAME'), + 'password' => getenv('SPARKPOST_SMTP_PASSWORD'), + 'encryption' => 'tls', + 'auth_mode' => 'plain', + ]; + } + + $app->register(new RoutingServiceProvider()) ->register(new ServiceControllerServiceProvider()) ->register(new HttpFragmentServiceProvider()); diff --git a/app/views/uikit.html.twig b/app/themes/default/base.html.twig similarity index 100% rename from app/views/uikit.html.twig rename to app/themes/default/base.html.twig diff --git a/app/views/hidden.html.twig b/app/themes/default/hidden.html.twig similarity index 100% rename from app/views/hidden.html.twig rename to app/themes/default/hidden.html.twig diff --git a/app/views/resume.html.twig b/app/themes/default/index.html.twig similarity index 98% rename from app/views/resume.html.twig rename to app/themes/default/index.html.twig index 550d17e..84201f1 100644 --- a/app/views/resume.html.twig +++ b/app/themes/default/index.html.twig @@ -1,4 +1,4 @@ -{% extends app.config.twig.template %} +{% extends 'base.html.twig' %} {% block title %} {{ app.config.app.title | default('Resume') }} @@ -175,10 +175,10 @@
- {{ form_label(contact_form.message) }} + {{ form_label(contact_form.message|raw) }}
- {{ form_widget(contact_form.message, {'attr':{'placeholder': "Please leave me a message. I will get back to you as soon as possible."}}) }}
- + {{ form_widget(contact_form.message) }} +
{{ form_label(contact_form.submit) }} @@ -207,7 +207,7 @@

Verify

Verify that you are a human please.

-
diff --git a/app/views/recaptcha.html.twig b/app/themes/default/recaptcha.html.twig similarity index 100% rename from app/views/recaptcha.html.twig rename to app/themes/default/recaptcha.html.twig diff --git a/composer.lock b/composer.lock index 25cd546..9660eca 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", "This file is @generated automatically" ], - "hash": "58b6f8f80e5846af7b9ae23a628a0861", + "hash": "e690fdd7168c515136615a9ee42053de", "content-hash": "9d13427d982ec276ccfba5e7fdc2cd8a", "packages": [ { diff --git a/src/Sikofitt/Controller/ApiControllerProvider.php b/src/Sikofitt/Controller/ApiControllerProvider.php index 549a0f3..f71d740 100644 --- a/src/Sikofitt/Controller/ApiControllerProvider.php +++ b/src/Sikofitt/Controller/ApiControllerProvider.php @@ -24,43 +24,155 @@ namespace Sikofitt\Controller; +use Prophecy\Doubler\ClassPatch\ReflectionClassNewInstancePatch; use ReCaptcha\ReCaptcha; +use Sikofitt\Form\Type\ContactType; use Silex\Api\ControllerProviderInterface; use Silex\Application; +use Silex\Provider\SecurityServiceProvider; use Symfony\Component\HttpFoundation\JsonResponse; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Validator\Constraints\Collection; +use Symfony\Component\Validator\Constraints\Email; +use Symfony\Component\Validator\Constraints\Length; +use Symfony\Component\Validator\Constraints\NotBlank; +use Symfony\Component\Validator\Constraints\EqualTo; +use Symfony\Component\Validator\ValidatorBuilder; -class ApiControllerProvider implements ControllerProviderInterface -{ - /** - * {@inheritdoc} - * - * @param Application $app - * - * @return mixed - */ - public function connect(Application $app) - { - $controllers = $app['controllers_factory']; +class ApiControllerProvider implements ControllerProviderInterface { + /** + * {@inheritdoc} + * + * @param Application $app + * + * @return mixed + */ + public function connect(Application $app) { + $controllers = $app['controllers_factory']; + $controllers->get('/v1/schema', function () use ($app) { + $response = new Response(file_get_contents($app->getDataDirectory() . '/schema/schema.v1.json'), Response::HTTP_OK); + $response->headers->set('Content-Type', 'application/schema+json'); - $controllers->get('/v1/schema', function () use ($app) { - - $response = new Response(file_get_contents($app->getDataDirectory() . '/schema/schema.v1.json'), Response::HTTP_OK); - $response->headers->set('Content-Type', 'application/schema+json'); - return $response; + return $response; }); - $controllers->match('/v1/message', function (Request $request) use ($app) { - /*$app->mail(\Swift_Message::newInstance() - ->setSubject('[YourSite] Feedback') - ->setFrom(array('noreply@yoursite.com')) - ->setTo(array('feedback@yoursite.com')) - ->setBody($request->get('message'))); */ - return new Response('This is the message.'); - })->method('GET|POST'); - $controllers->post('/v1/captcha', function (Request $request) use ($app) { + + $controllers->match('/v1/message', function (Request $request) use ($app) { + + static $code = 255; + $returnData = [ + 'status' => 'error', + 'message' => 'Unknown error.', + 'code' => $code, + ]; + $csrf = $request->getSession()->get('_csrf/contact') ?: false; + + // Set some validation constraints + $constraints = [ + 'contact' => new Collection([ + 'name' => [ + new Length([ + 'min' => 4, + 'minMessage' => 'Name must be at least 4 characters.', + ] + ), + new NotBlank([ + 'message' => 'Name must not be blank.', + ] + ), + ], + 'email' => [ + new Email([ + 'message' => 'Invalid email', + ]), + new NotBlank([ + 'message' => 'Email must not be blank.', + ]), + ], + 'message' => [ + new Length([ + 'min' => 20, + 'minMessage' => 'Message must be at least 20 characters.', + ]), + new NotBlank([ + 'message' => 'Message must not be blank', + ]), + ], + '_token' => [ + new EqualTo(['value' => $csrf, 'message' => 'Invalid token.']), + ] + ] + ), + ]; + + $contactFormData = $request->request->all(); + + $valid = $app['validator']->validate($contactFormData, new Collection($constraints)); + + if(count($valid) > 0) { + $sanitizeProperty = function() use ($valid) { + return str_replace(['][', '[', ']'], ['_','',''], $valid[0]->getPropertyPath()); + }; + + return new JsonResponse([ + 'status' => 'error', + 'message' => $valid[0]->getMessage(), + 'id' => $sanitizeProperty(), + 'const' => $valid[0]->getCode(), + 'code' => 256 + ], 256); + + } else { + + $contactFormName = $contactFormData['contact']['name']; + $contactFormEmail = $contactFormData['contact']['email']; + $contactFormMessage = $contactFormData['contact']['message']; + $request->getSession()->remove('_csrf/contact'); + + try { + $app->mail(\Swift_Message::newInstance() + ->setSubject('[Resume] Message') + ->setFrom([$contactFormEmail => $contactFormName]) + ->setTo($app->config('app.email')) + ->setBody($contactFormMessage) + ); + $returnData = [ + 'status' => 'success', + 'message' => 'Message successfully sent.', + 'code' => 201, + 'data' => $contactFormData, + ]; + } catch(\Exception $e) { + $returnData = [ + 'status' => 'error', + 'message' => 'Could not send message.', + 'code' => 256, + 'data' => $e->getMessage(), + ]; + } + + return new JsonResponse($returnData, 201); + } + + })->method('GET|POST')->bind('api_message'); + $controllers->get('/v1/emailTest', function(Request $request) use ($app) { + try { + $app->mail(\Swift_Message::newInstance() + ->setSubject('[Resume] Message') + ->setFrom(['eric@rewiv.com' => 'Eric']) + ->setTo('eric@ericwheeler.net') + ->setBody('Testing message.') + ); + } + catch(\Exception $e) { + dump($e->getMessage()); + } + return new Response('Hello'); + }); + + $controllers->post('/v1/captcha', function (Request $request) use ($app) { $captcha = new ReCaptcha('6LcvmSQTAAAAAITkvYJjgLar1LqGGLz-ic0ZMiXo'); $valid = $captcha->verify( @@ -68,38 +180,39 @@ class ApiControllerProvider implements ControllerProviderInterface $request->server->get('REMOTE_ADDR') ); if ($valid->isSuccess()) { - $return = [ - 'valid' => true, + $return = [ + 'valid' => true, 'message' => [ '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.', ], ]; - } else { - $errorCodes = [ - 'missing-input-secret' => 'The secret parameter is missing.', - 'invalid-input-secret' => 'The secret parameter is invalid or malformed.', - 'missing-input-response' =>'The response parameter is missing.', - 'invalid-input-response' => 'The response parameter is invalid or malformed.' + } + else { + $errorCodes = [ + 'missing-input-secret' => 'The secret parameter is missing.', + 'invalid-input-secret' => 'The secret parameter is invalid or malformed.', + 'missing-input-response' => 'The response parameter is missing.', + 'invalid-input-response' => 'The response parameter is invalid or malformed.', ]; - foreach ($valid->getErrorCodes() as $code) { - if (array_key_exists($code, $errorCodes)) { - $errors[] = $errorCodes[$code]; - } + foreach ($valid->getErrorCodes() as $code) { + if (array_key_exists($code, $errorCodes)) { + $errors[] = $errorCodes[$code]; } - if (!isset($errors)) { - $errors[] = 'An unknown error occurred.'; - } - $return = [ - 'valid' => false, + } + if (!isset($errors)) { + $errors[] = 'An unknown error occurred.'; + } + $return = [ + 'valid' => false, 'message' => $errors, ]; } return new JsonResponse(json_encode($return)); - }); + })->bind('api_captcha'); - return $controllers; - } + return $controllers; + } } diff --git a/src/Sikofitt/Controller/ResumeControllerProvider.php b/src/Sikofitt/Controller/ResumeControllerProvider.php index c299621..be4f731 100644 --- a/src/Sikofitt/Controller/ResumeControllerProvider.php +++ b/src/Sikofitt/Controller/ResumeControllerProvider.php @@ -46,7 +46,7 @@ class ResumeControllerProvider implements ControllerProviderInterface $controllers->get('/', function (Request $request) use ($app, $contactForm) { - return $app['twig']->render('resume.html.twig', [ + return $app['twig']->render('index.html.twig', [ 'fullData' => $this->resumeData, 'basics' => $this->getResumeBasics(), diff --git a/src/Sikofitt/Form/Type/ContactType.php b/src/Sikofitt/Form/Type/ContactType.php index 8b45c1a..ca891e1 100644 --- a/src/Sikofitt/Form/Type/ContactType.php +++ b/src/Sikofitt/Form/Type/ContactType.php @@ -32,14 +32,15 @@ use Symfony\Component\Validator\Constraints\NotBlank; class ContactType extends AbstractType { - - /** + /** * @param FormBuilderInterface $builder * @param array $options */ public function buildForm(FormBuilderInterface $builder, array $options) { + $builder + ->setAction('/api/v1/message') ->add('name', TextType::class, [ 'constraints' => [ new Length([ @@ -53,7 +54,7 @@ class ContactType extends AbstractType ], 'label' => 'Name', 'label_attr' => [ - 'class' => 'uk-form-label', + 'class' => 'uk-hidden-small uk-form-label', ], ]) ->add('email', EmailType::class, [ @@ -67,7 +68,7 @@ class ContactType extends AbstractType ], 'label' => 'Email', 'label_attr' => [ - 'class' => 'uk-form-label', + 'class' => 'uk-hidden-small uk-form-label', ], ]) ->add('message', TextareaType::class, [ @@ -75,7 +76,7 @@ class ContactType extends AbstractType 'rows' => 5, 'cols' => 40, 'class' => 'uk-form-large uk-width-1-1', - 'placeholder' => 'Please leave me a message, I will get back to you as soon as possible.', + 'placeholder' => 'Please leave me a message. I will get back to you as soon as possible.', ], 'constraints' => [ new NotBlank(), @@ -84,8 +85,9 @@ class ContactType extends AbstractType ]), ], 'label_attr' => [ - 'class' => 'uk-invisible uk-form-label', + 'class' => 'uk-hidden-small uk-form-label', ], + 'label' => 'If you prefer you can send me a message at eric@ericwheeler.net', ]) ->add('submit', SubmitType::class, [ @@ -109,6 +111,7 @@ class ContactType extends AbstractType $resolver->setDefaults([ 'attr' => [ 'class' => 'uk-form uk-form-horizontal', + 'onsubmit' => 'return false;', ], ]); } diff --git a/src/Sikofitt/js/resume.js b/src/Sikofitt/js/resume.js index 8feef42..0c57109 100644 --- a/src/Sikofitt/js/resume.js +++ b/src/Sikofitt/js/resume.js @@ -32,5 +32,35 @@ jq(document).ready(function (jq) { } }); + }); // Phone form + jq('form[name=contact]').on('submit', function(event) { + jq.post(jq(this).attr('action'), jq(this).serialize(), function(response) { + if(response.status !== 'success') { + jq('#' + response.id).addClass('uk-form-danger'); + UIkit.notify('' + response.message + ' ('+response.code+')
', { + pos: 'top-center', + status: 'danger' + }); + console.log(response); + } else { + console.log(response); + UIkit.notify(' ' + response.message, { + pos: 'top-center', + status: 'success' + }); + var $wrapper = jq('#contact-form'), + $button = jq(''), + $thankYouText = jq('

Thank you for your message.

I will get back to you as soon as possible.

'); + $wrapper.empty().append($thankYouText).append($button); + jq('a[href="#contact-form-wrapper"]').replaceWith('eric@ericwheeler.net'); + } + }); + + }); + + jq('form[name=contact] input, form[name=contact] textarea').on('focus', function() { + if(jq(this).hasClass('uk-form-danger')) { + jq(this).removeClass('uk-form-danger'); + } }); }); \ No newline at end of file diff --git a/src/Sikofitt/less/resume.less b/src/Sikofitt/less/resume.less index a212e53..1fead3e 100644 --- a/src/Sikofitt/less/resume.less +++ b/src/Sikofitt/less/resume.less @@ -49,8 +49,17 @@ a.profile-link { border-bottom: 1px solid #ccc; padding-bottom: 12px; } -.uk-close { - &:after { - font-size:30px; +.uk-modal-dialog-blank { + .uk-close { + &:after { + font-size: 30px; + } } +} +.uk-button-massive { + min-height: 64px; + padding: 0 36px; + line-height: 40px; + font-size: 34px; + border-radius: 2px; } \ No newline at end of file