├── www
├── libs
│ └── .gitkeep
├── robots.txt
├── adminer.php
├── favicon.ico
├── flags
│ ├── flags.png
│ └── flags.min.css
├── img
│ ├── octopus.png
│ ├── og-image.jpg
│ ├── spinner.gif
│ ├── eventigo-header.png
│ ├── header-background.jpg
│ └── header-background-old.jpg
├── favicon
│ ├── favicon.ico
│ ├── mstile-70x70.png
│ ├── favicon-16x16.png
│ ├── favicon-32x32.png
│ ├── favicon-96x96.png
│ ├── mstile-144x144.png
│ ├── mstile-150x150.png
│ ├── mstile-310x150.png
│ ├── mstile-310x310.png
│ ├── apple-touch-icon.png
│ ├── android-chrome-144x144.png
│ ├── android-chrome-192x192.png
│ ├── android-chrome-36x36.png
│ ├── android-chrome-48x48.png
│ ├── android-chrome-72x72.png
│ ├── android-chrome-96x96.png
│ ├── apple-touch-icon-57x57.png
│ ├── apple-touch-icon-60x60.png
│ ├── apple-touch-icon-72x72.png
│ ├── apple-touch-icon-76x76.png
│ ├── apple-touch-icon-114x114.png
│ ├── apple-touch-icon-120x120.png
│ ├── apple-touch-icon-144x144.png
│ ├── apple-touch-icon-152x152.png
│ ├── apple-touch-icon-180x180.png
│ ├── apple-touch-icon-precomposed.png
│ ├── browserconfig.xml
│ ├── manifest.json
│ └── safari-pinned-tab.svg
├── css
│ ├── newsletter.css
│ ├── newsletter.less
│ ├── palette.less
│ ├── style.css
│ ├── admin.css
│ └── admin.less
├── index.php
├── js
│ ├── admin.js
│ ├── lib.js
│ └── plugins
│ │ └── eventFilter.js
├── .maintenance.php
├── web.config
└── .htaccess
├── .bowerrc
├── log
├── .htaccess
└── web.config
├── temp
├── .htaccess
└── web.config
├── app
├── .htaccess
├── config
│ ├── decorator.neon
│ ├── extensions
│ │ ├── translation.neon
│ │ ├── facebook.neon
│ │ ├── restful.neon
│ │ └── console.neon
│ ├── database.neon
│ ├── parameters.neon
│ ├── config.neon
│ ├── templates
│ │ └── config.local.neon
│ ├── factories.neon
│ └── services.neon
├── Modules
│ ├── Front
│ │ ├── Components
│ │ │ ├── Subscription
│ │ │ │ ├── Subscription.latte
│ │ │ │ ├── SubscriptionFactoryInterface.php
│ │ │ │ └── Subscription.php
│ │ │ ├── Tags
│ │ │ │ ├── TagsFactoryInterface.php
│ │ │ │ ├── Tags.latte
│ │ │ │ └── Tags.php
│ │ │ ├── Sign
│ │ │ │ ├── SignInFactoryInterface.php
│ │ │ │ ├── SignIn.latte
│ │ │ │ └── SignIn.php
│ │ │ ├── Settings
│ │ │ │ ├── SettingsFactoryInterface.php
│ │ │ │ └── Settings.php
│ │ │ ├── SubscriptionTags
│ │ │ │ ├── SubscriptionTagsFactoryInterface.php
│ │ │ │ └── SubscriptionTags.latte
│ │ │ └── EventsList
│ │ │ │ ├── EventsListFactoryInterface.php
│ │ │ │ └── EventsList.php
│ │ ├── Model
│ │ │ ├── Exceptions
│ │ │ │ └── Subscription
│ │ │ │ │ └── EmailExistsException.php
│ │ │ └── EventsIterator.php
│ │ └── Presenters
│ │ │ ├── templates
│ │ │ ├── Homepage
│ │ │ │ ├── discover.latte
│ │ │ │ └── default.latte
│ │ │ └── Profile
│ │ │ │ └── settings.latte
│ │ │ ├── SignPresenter.php
│ │ │ ├── RedirectPresenter.php
│ │ │ ├── AbstractBasePresenter.php
│ │ │ └── ProfilePresenter.php
│ ├── Core
│ │ ├── Presenters
│ │ │ ├── templates
│ │ │ │ └── Error
│ │ │ │ │ ├── 4xx.latte
│ │ │ │ │ ├── 405.latte
│ │ │ │ │ ├── 410.latte
│ │ │ │ │ ├── 403.latte
│ │ │ │ │ ├── 404.latte
│ │ │ │ │ └── 500.phtml
│ │ │ ├── Error4xxPresenter.php
│ │ │ ├── ErrorPresenter.php
│ │ │ └── AbstractBasePresenter.php
│ │ ├── Model
│ │ │ ├── CountryModel.php
│ │ │ ├── OrganiserModel.php
│ │ │ ├── TagGroupModel.php
│ │ │ ├── EventSeriesModel.php
│ │ │ ├── EventRedirectModel.php
│ │ │ ├── EventSources
│ │ │ │ ├── AbstractEventSource.php
│ │ │ │ ├── Utils
│ │ │ │ │ └── EventSource.php
│ │ │ │ ├── Srazy
│ │ │ │ │ └── SrazyEventSource.php
│ │ │ │ ├── Meetup
│ │ │ │ │ └── MeetupEventSource.php
│ │ │ │ └── Facebook
│ │ │ │ │ └── FacebookEventSource.php
│ │ │ ├── EventTagModel.php
│ │ │ ├── UserTagModel.php
│ │ │ ├── Entity
│ │ │ │ ├── Tag.php
│ │ │ │ └── EventTag.php
│ │ │ ├── AbstractIterator.php
│ │ │ ├── AbstractBaseModel.php
│ │ │ └── TagModel.php
│ │ ├── Components
│ │ │ ├── Form
│ │ │ │ └── Form.php
│ │ │ └── AbstractBaseControl.php
│ │ └── Utils
│ │ │ ├── Collection.php
│ │ │ ├── Helper.php
│ │ │ ├── Filters.php
│ │ │ └── DateTime.php
│ ├── Newsletter
│ │ ├── Model
│ │ │ ├── NoEventsFoundException.php
│ │ │ ├── Exception.php
│ │ │ ├── UserNewsletterModel.php
│ │ │ ├── NewsletterModel.php
│ │ │ └── Entity
│ │ │ │ └── Newsletter.php
│ │ ├── Presenters
│ │ │ ├── templates
│ │ │ │ ├── Newsletter
│ │ │ │ │ ├── default.latte
│ │ │ │ │ ├── unsubscribe.latte
│ │ │ │ │ └── newEvents.latte
│ │ │ │ └── @layout.latte
│ │ │ └── NewsletterPresenter.php
│ │ └── Console
│ │ │ ├── CreateNewsletterCommand.php
│ │ │ ├── SendNewslettersCommand.php
│ │ │ └── RenderNewslettersCommand.php
│ ├── Admin
│ │ ├── Components
│ │ │ ├── SignIn
│ │ │ │ ├── SignInFormFactoryInterface.php
│ │ │ │ └── SignInForm.php
│ │ │ ├── EventForm
│ │ │ │ └── EventFormFactoryInterface.php
│ │ │ ├── SourceForm
│ │ │ │ ├── SourceFormFactoryInterface.php
│ │ │ │ └── SourceForm.php
│ │ │ ├── SourcesTable
│ │ │ │ └── SourcesTableFactoryInterface.php
│ │ │ ├── EventsTable
│ │ │ │ ├── NotApprovedEventsTableFactoryInterface.php
│ │ │ │ ├── NotApprovedEventsTable.latte
│ │ │ │ └── js.latte
│ │ │ └── DataTable
│ │ │ │ └── AbstractDataTable.php
│ │ ├── Presenters
│ │ │ ├── AbstractBasePresenter.php
│ │ │ ├── templates
│ │ │ │ ├── Sources
│ │ │ │ │ ├── create.latte
│ │ │ │ │ └── default.latte
│ │ │ │ ├── Sign
│ │ │ │ │ ├── in.latte
│ │ │ │ │ └── @layout.latte
│ │ │ │ ├── Events
│ │ │ │ │ ├── default.latte
│ │ │ │ │ └── create.latte
│ │ │ │ └── Dashboard
│ │ │ │ │ └── default.latte
│ │ │ ├── ExceptionPresenter.php
│ │ │ ├── DashboardPresenter.php
│ │ │ ├── SignPresenter.php
│ │ │ └── SourcesPresenter.php
│ │ ├── Model
│ │ │ ├── SourceModel.php
│ │ │ ├── OrganiserService.php
│ │ │ ├── Authenticator.php
│ │ │ └── SourceService.php
│ │ └── Console
│ │ │ ├── CrawlSourcesCommand.php
│ │ │ └── GeneratePasswordCommand.php
│ ├── Api
│ │ └── V1
│ │ │ ├── Presenters
│ │ │ └── EventsPresenter.php
│ │ │ └── Model
│ │ │ └── EventApiService.php
│ └── Email
│ │ ├── Model
│ │ ├── Entity
│ │ │ ├── Email.php
│ │ │ └── BasicEmail.php
│ │ └── EmailService.php
│ │ └── Presenters
│ │ └── EmailPresenter.php
├── lang
│ ├── tags.cs_CZ.neon
│ ├── newsletter.cs_CZ.neon
│ ├── email.cs_CZ.neon
│ └── front.cs_CZ.neon
├── web.config
├── bootstrap.php
└── Router
│ └── RouterFactory.php
├── reset.sh
├── events.sql.zip
├── resources
├── favicon.psd
└── eventigo-header.psd
├── .editorconfig
├── .gitignore
├── tests
├── config
│ └── config.test.neon
├── ContainerFactory.php
└── Modules
│ └── Newsletter
│ └── Model
│ └── NewsletterServiceTest.php
├── db
└── migrations
│ ├── 20160329220253_user_newsletter_html.php
│ ├── 20160413064329_user_token.php
│ ├── 20160320055050_bigger_code.php
│ ├── 20160428174045_user_last_access.php
│ ├── 20160317185357_password_column.php
│ ├── 20160406194807_tag_group_icon.php
│ ├── 20160514211953_event_approved.php
│ ├── 20170205232804_demo_user.php
│ ├── 20160514061722_event_state.php
│ ├── 20170627065214_abroad_events.php
│ ├── 20160309173103_nullable_columns.php
│ ├── 20160505142316_newsletter_table.php
│ ├── 20170626060205_venue.php
│ ├── 20160323174545_event_redirect.php
│ ├── 20160512205838_events_unique_index.php
│ ├── 20160504063746_facebook.php
│ ├── 20160324192410_users_events.php
│ ├── 20160512150334_newsletter_content_limit.php
│ ├── 20160327201056_sources.php
│ ├── 20160402220653_tag_group.php
│ ├── 20160322091004_users_events_series_and_organisers.php
│ ├── 20160421173837_newsletter_refactoring.php
│ └── 20160318171343_organisers.php
├── bower.json
├── bin
└── console
├── phpunit.xml
├── .travis.yml
├── phinx.yml.template
├── phpstan.neon
├── composer.json
├── easy-coding-standard.neon
└── README.md
/www/libs/.gitkeep:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/www/robots.txt:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/.bowerrc:
--------------------------------------------------------------------------------
1 | {
2 | "directory": "www/libs"
3 | }
--------------------------------------------------------------------------------
/log/.htaccess:
--------------------------------------------------------------------------------
1 | Order Allow,Deny
2 | Deny from all
--------------------------------------------------------------------------------
/temp/.htaccess:
--------------------------------------------------------------------------------
1 | Order Allow,Deny
2 | Deny from all
--------------------------------------------------------------------------------
/app/.htaccess:
--------------------------------------------------------------------------------
1 | Order Allow,Deny
2 | Deny from all
3 |
--------------------------------------------------------------------------------
/reset.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | rm -rf temp/cache/*
4 | echo "Cache cleaned"
5 |
--------------------------------------------------------------------------------
/events.sql.zip:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/filipsuk/eventigo-web/HEAD/events.sql.zip
--------------------------------------------------------------------------------
/www/adminer.php:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/filipsuk/eventigo-web/HEAD/www/adminer.php
--------------------------------------------------------------------------------
/www/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/filipsuk/eventigo-web/HEAD/www/favicon.ico
--------------------------------------------------------------------------------
/www/flags/flags.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/filipsuk/eventigo-web/HEAD/www/flags/flags.png
--------------------------------------------------------------------------------
/www/img/octopus.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/filipsuk/eventigo-web/HEAD/www/img/octopus.png
--------------------------------------------------------------------------------
/www/img/og-image.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/filipsuk/eventigo-web/HEAD/www/img/og-image.jpg
--------------------------------------------------------------------------------
/www/img/spinner.gif:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/filipsuk/eventigo-web/HEAD/www/img/spinner.gif
--------------------------------------------------------------------------------
/resources/favicon.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/filipsuk/eventigo-web/HEAD/resources/favicon.psd
--------------------------------------------------------------------------------
/www/favicon/favicon.ico:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/filipsuk/eventigo-web/HEAD/www/favicon/favicon.ico
--------------------------------------------------------------------------------
/www/favicon/mstile-70x70.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/filipsuk/eventigo-web/HEAD/www/favicon/mstile-70x70.png
--------------------------------------------------------------------------------
/www/img/eventigo-header.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/filipsuk/eventigo-web/HEAD/www/img/eventigo-header.png
--------------------------------------------------------------------------------
/app/config/decorator.neon:
--------------------------------------------------------------------------------
1 | decorator:
2 | App\Modules\Core\Components\AbstractBaseControl:
3 | inject: true
--------------------------------------------------------------------------------
/resources/eventigo-header.psd:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/filipsuk/eventigo-web/HEAD/resources/eventigo-header.psd
--------------------------------------------------------------------------------
/www/favicon/favicon-16x16.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/filipsuk/eventigo-web/HEAD/www/favicon/favicon-16x16.png
--------------------------------------------------------------------------------
/www/favicon/favicon-32x32.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/filipsuk/eventigo-web/HEAD/www/favicon/favicon-32x32.png
--------------------------------------------------------------------------------
/www/favicon/favicon-96x96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/filipsuk/eventigo-web/HEAD/www/favicon/favicon-96x96.png
--------------------------------------------------------------------------------
/www/favicon/mstile-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/filipsuk/eventigo-web/HEAD/www/favicon/mstile-144x144.png
--------------------------------------------------------------------------------
/www/favicon/mstile-150x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/filipsuk/eventigo-web/HEAD/www/favicon/mstile-150x150.png
--------------------------------------------------------------------------------
/www/favicon/mstile-310x150.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/filipsuk/eventigo-web/HEAD/www/favicon/mstile-310x150.png
--------------------------------------------------------------------------------
/www/favicon/mstile-310x310.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/filipsuk/eventigo-web/HEAD/www/favicon/mstile-310x310.png
--------------------------------------------------------------------------------
/www/img/header-background.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/filipsuk/eventigo-web/HEAD/www/img/header-background.jpg
--------------------------------------------------------------------------------
/www/favicon/apple-touch-icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/filipsuk/eventigo-web/HEAD/www/favicon/apple-touch-icon.png
--------------------------------------------------------------------------------
/www/img/header-background-old.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/filipsuk/eventigo-web/HEAD/www/img/header-background-old.jpg
--------------------------------------------------------------------------------
/app/Modules/Front/Components/Subscription/Subscription.latte:
--------------------------------------------------------------------------------
1 | {form form}
2 | {input email}
3 | {input subscribe}
4 | {/form}
--------------------------------------------------------------------------------
/www/favicon/android-chrome-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/filipsuk/eventigo-web/HEAD/www/favicon/android-chrome-144x144.png
--------------------------------------------------------------------------------
/www/favicon/android-chrome-192x192.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/filipsuk/eventigo-web/HEAD/www/favicon/android-chrome-192x192.png
--------------------------------------------------------------------------------
/www/favicon/android-chrome-36x36.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/filipsuk/eventigo-web/HEAD/www/favicon/android-chrome-36x36.png
--------------------------------------------------------------------------------
/www/favicon/android-chrome-48x48.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/filipsuk/eventigo-web/HEAD/www/favicon/android-chrome-48x48.png
--------------------------------------------------------------------------------
/www/favicon/android-chrome-72x72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/filipsuk/eventigo-web/HEAD/www/favicon/android-chrome-72x72.png
--------------------------------------------------------------------------------
/www/favicon/android-chrome-96x96.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/filipsuk/eventigo-web/HEAD/www/favicon/android-chrome-96x96.png
--------------------------------------------------------------------------------
/www/favicon/apple-touch-icon-57x57.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/filipsuk/eventigo-web/HEAD/www/favicon/apple-touch-icon-57x57.png
--------------------------------------------------------------------------------
/www/favicon/apple-touch-icon-60x60.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/filipsuk/eventigo-web/HEAD/www/favicon/apple-touch-icon-60x60.png
--------------------------------------------------------------------------------
/www/favicon/apple-touch-icon-72x72.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/filipsuk/eventigo-web/HEAD/www/favicon/apple-touch-icon-72x72.png
--------------------------------------------------------------------------------
/www/favicon/apple-touch-icon-76x76.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/filipsuk/eventigo-web/HEAD/www/favicon/apple-touch-icon-76x76.png
--------------------------------------------------------------------------------
/www/favicon/apple-touch-icon-114x114.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/filipsuk/eventigo-web/HEAD/www/favicon/apple-touch-icon-114x114.png
--------------------------------------------------------------------------------
/www/favicon/apple-touch-icon-120x120.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/filipsuk/eventigo-web/HEAD/www/favicon/apple-touch-icon-120x120.png
--------------------------------------------------------------------------------
/www/favicon/apple-touch-icon-144x144.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/filipsuk/eventigo-web/HEAD/www/favicon/apple-touch-icon-144x144.png
--------------------------------------------------------------------------------
/www/favicon/apple-touch-icon-152x152.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/filipsuk/eventigo-web/HEAD/www/favicon/apple-touch-icon-152x152.png
--------------------------------------------------------------------------------
/www/favicon/apple-touch-icon-180x180.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/filipsuk/eventigo-web/HEAD/www/favicon/apple-touch-icon-180x180.png
--------------------------------------------------------------------------------
/www/favicon/apple-touch-icon-precomposed.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/filipsuk/eventigo-web/HEAD/www/favicon/apple-touch-icon-precomposed.png
--------------------------------------------------------------------------------
/app/lang/tags.cs_CZ.neon:
--------------------------------------------------------------------------------
1 | tagGroup.business: Podnikání
2 | tagGroup.development: Programování
3 | tagGroup.marketing: Marketing
4 | tagGroup.others: Další
--------------------------------------------------------------------------------
/app/config/extensions/translation.neon:
--------------------------------------------------------------------------------
1 | extensions:
2 | translation: Kdyby\Translation\DI\TranslationExtension
3 |
4 | translation:
5 | default: cs
6 | fallback: [cs_CZ, cs]
--------------------------------------------------------------------------------
/app/config/database.neon:
--------------------------------------------------------------------------------
1 | database:
2 | dsn: 'mysql:host=%database.host%;dbname=%database.name%'
3 | user: %database.username%
4 | password: %database.password%
5 | options:
6 | lazy: yes
7 |
--------------------------------------------------------------------------------
/app/Modules/Core/Presenters/templates/Error/4xx.latte:
--------------------------------------------------------------------------------
1 | {block content}
2 |
Oops...
3 |
4 | Your browser sent a request that this server could not understand or process.
5 |
--------------------------------------------------------------------------------
/app/Modules/Newsletter/Model/NoEventsFoundException.php:
--------------------------------------------------------------------------------
1 | Method Not Allowed
3 |
4 | The requested method is not allowed for the URL.
5 |
6 | error 405
7 |
--------------------------------------------------------------------------------
/app/Modules/Newsletter/Model/Exception.php:
--------------------------------------------------------------------------------
1 |
5 | {$userNewsletter['content']|noescape}
6 |
7 | {/block}
8 |
--------------------------------------------------------------------------------
/app/lang/newsletter.cs_CZ.neon:
--------------------------------------------------------------------------------
1 | unsubscribe.unsubscribed: "No dobrá. My si email %email% zatím škrtáme, ale brzy na viděnou (ツ)"
2 | email.events.nextWeek: "Příští týden"
3 | email.events.selectedNewEvents: "Vybrané nové akce"
4 |
--------------------------------------------------------------------------------
/app/Modules/Front/Components/Sign/SignInFactoryInterface.php:
--------------------------------------------------------------------------------
1 | Page Not Found
3 |
4 | The page you requested has been taken off the site. We apologize for the inconvenience.
5 |
6 | error 410
7 |
--------------------------------------------------------------------------------
/app/Modules/Front/Components/Settings/SettingsFactoryInterface.php:
--------------------------------------------------------------------------------
1 |
5 | {_newsletter.unsubscribe.unsubscribed, [email => $email]|noescape}
6 |
7 | {/block}
8 |
--------------------------------------------------------------------------------
/app/Modules/Front/Components/Subscription/SubscriptionFactoryInterface.php:
--------------------------------------------------------------------------------
1 | getByType('Nette\Application\Application')->run();
9 |
--------------------------------------------------------------------------------
/www/js/admin.js:
--------------------------------------------------------------------------------
1 | $(function() {
2 | $('select').select2({
3 | width: '100%'
4 | });
5 |
6 | $('.datetime').datetimepicker({
7 | locale: 'cs',
8 | format: "D. M. YYYY LT"
9 | });
10 |
11 | $('.date').datetimepicker({
12 | locale: 'cs',
13 | format: "D. M. YYYY"
14 | });
15 | });
--------------------------------------------------------------------------------
/app/Modules/Front/Components/Sign/SignIn.latte:
--------------------------------------------------------------------------------
1 | {snippet form}
2 |
3 |
4 | {="front.signIn.form.title"|translate}
5 |
6 | {form form, class => ajax}
7 | {input email}
8 | {input submit, class => 'btn btn-success'}
9 | {/form}
10 | {/snippet}
--------------------------------------------------------------------------------
/app/Modules/Core/Model/EventSeriesModel.php:
--------------------------------------------------------------------------------
1 | Access Denied
3 |
4 | You do not have permission to view this page. Please try contact the web
5 | site administrator if you believe you should be able to view this page.
6 |
7 | error 403
8 |
--------------------------------------------------------------------------------
/app/lang/email.cs_CZ.neon:
--------------------------------------------------------------------------------
1 | login.loginButton: "Přihlásit se"
2 | login.text: "Pro přihlášení klikni na tlačítko níže. Nevedeme uživatelská hesla, protože si je stejně nikdo nepamatuje."
3 | login.footerText: "Tento e-mail jsi dostal na základě pokusu o přihlášení na eventigo.cz."
4 | login.subject: Přihlášení na eventigo.cz
5 |
--------------------------------------------------------------------------------
/tests/config/config.test.neon:
--------------------------------------------------------------------------------
1 | parameters:
2 | meetup:
3 | apiKey: ''
4 |
5 | SendGrid:
6 | ApiKey: ''
7 |
8 | facebook:
9 | appSecret: "default_secret_to_confirm_format"
10 |
11 | database:
12 | dsn: 'mysql:host=127.0.0.1:1234;dbname=events'
13 | user:
14 | password:
15 | options:
16 | lazy: yes
17 |
--------------------------------------------------------------------------------
/app/Modules/Admin/Components/SourcesTable/SourcesTableFactoryInterface.php:
--------------------------------------------------------------------------------
1 | {="newsletter.email.events.selectedNewEvents"|translate}
2 |
3 | {$e->getName()} ({str_replace(' ', ' ', $e->getStart()->format('j. n.'))|noescape} {$e->getVenue()})
4 |
5 |
--------------------------------------------------------------------------------
/www/js/lib.js:
--------------------------------------------------------------------------------
1 | Array.prototype.prefix = function (prefix) {
2 | var arr = [];
3 | for (i = 0; i < this.length; i++) {
4 | arr[i] = prefix + this[i];
5 | }
6 | return arr;
7 | };
8 |
9 | Array.prototype.diff = function (a) {
10 | return this.filter(function (i) {
11 | return a.indexOf(i) < 0;
12 | });
13 | };
--------------------------------------------------------------------------------
/db/migrations/20160329220253_user_newsletter_html.php:
--------------------------------------------------------------------------------
1 | table('users_newsletters')
10 | ->addColumn('html', 'text', ['after' => 'variables'])
11 | ->update();
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/db/migrations/20160413064329_user_token.php:
--------------------------------------------------------------------------------
1 | table('users')
10 | ->addColumn('token', 'string', ['limit' => 64, 'after' => 'password', 'null' => true])
11 | ->update();
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/app/Modules/Core/Presenters/templates/Error/404.latte:
--------------------------------------------------------------------------------
1 | {block content}
2 | Page Not Found
3 |
4 | The page you requested could not be found. It is possible that the address is
5 | incorrect, or that the page no longer exists. Please use a search engine to find
6 | what you are looking for.
7 |
8 | error 404
9 |
--------------------------------------------------------------------------------
/app/config/parameters.neon:
--------------------------------------------------------------------------------
1 | parameters:
2 | baseUrl: 'https://eventigo.cz'
3 |
4 | newsletter:
5 | sendNewsletterWithNoEvents: true
6 | defaultSubject: Akce na příští týden
7 | defaultAuthor:
8 | name: Filip
9 | email: filip@eventigo.cz
10 |
11 | database:
12 | host: 127.0.0.1:8889
13 | name: events
14 | username:
15 | password:
16 |
--------------------------------------------------------------------------------
/db/migrations/20160320055050_bigger_code.php:
--------------------------------------------------------------------------------
1 | table('tags')
10 | ->changeColumn('code', 'string', ['limit' => 32, 'null' => false])
11 | ->update();
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/app/config/extensions/restful.neon:
--------------------------------------------------------------------------------
1 | extensions:
2 | restful: Drahak\Restful\DI\RestfulExtension
3 |
4 | restful:
5 | convention: 'snake_case'
6 | cacheDir: '%tempDir%/cache'
7 | jsonpKey: FALSE
8 | prettyPrintKey: 'pretty'
9 | routes:
10 | generateAtStart: FALSE
11 | autoGenerated: FALSE
12 | panel: TRUE
13 |
14 | security:
15 | requestTimeout: 300
16 |
--------------------------------------------------------------------------------
/app/Modules/Admin/Components/EventsTable/NotApprovedEventsTableFactoryInterface.php:
--------------------------------------------------------------------------------
1 | getByType(Symfony\Component\Console\Application::class);
9 | $consoleApplication->run();
10 |
--------------------------------------------------------------------------------
/db/migrations/20160428174045_user_last_access.php:
--------------------------------------------------------------------------------
1 | table('users')
10 | ->addColumn('last_access', 'datetime', ['null' => true, 'after' => 'newsletter'])
11 | ->update();
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/db/migrations/20160317185357_password_column.php:
--------------------------------------------------------------------------------
1 | table('users')
10 | ->addColumn('password', 'string', ['limit' => 152, 'after' => 'email', 'null' => true])
11 | ->update();
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/db/migrations/20160406194807_tag_group_icon.php:
--------------------------------------------------------------------------------
1 | table('tags_groups')
10 | ->addColumn('icon', 'string', ['limit' => 255, 'null' => true, 'after' => 'name'])
11 | ->update();
12 | }
13 | }
14 |
--------------------------------------------------------------------------------
/db/migrations/20160514211953_event_approved.php:
--------------------------------------------------------------------------------
1 | table('events')
10 | ->addColumn('approved', 'datetime', ['after' => 'state', 'null' => true])
11 | ->update();
12 |
13 | $this->execute("UPDATE events SET approved = created");
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/db/migrations/20170205232804_demo_user.php:
--------------------------------------------------------------------------------
1 | 'demo@gmail.com',
11 | 'password' => '$2y$10$00l9Qn.Ou7xaiHgT0fJpHuwbC3mbjYqQF4SdV3H616W4nKoagwhdy', // demo
12 | 'newsletter' => true
13 | ];
14 |
15 | $this->insert('users', $demoUser);
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/app/config/extensions/console.neon:
--------------------------------------------------------------------------------
1 | extensions:
2 | console: Contributte\Console\DI\ConsoleExtension
3 |
4 | console:
5 | url: %baseUrl%
6 |
7 | services:
8 | - App\Modules\Newsletter\Console\CreateNewsletterCommand
9 | - App\Modules\Newsletter\Console\RenderNewslettersCommand
10 | - App\Modules\Newsletter\Console\SendNewslettersCommand
11 | - App\Modules\Admin\Console\GeneratePasswordCommand
12 | - App\Modules\Admin\Console\CrawlSourcesCommand
13 |
--------------------------------------------------------------------------------
/db/migrations/20160514061722_event_state.php:
--------------------------------------------------------------------------------
1 | table('events')
10 | ->addColumn('state', 'enum', ['values' => ['approved', 'not-approved', 'skip'],
11 | 'default' => 'not-approved', 'after' => 'rate'])
12 | ->update();
13 |
14 | $this->execute('UPDATE events SET state = "approved"');
15 | }
16 | }
17 |
--------------------------------------------------------------------------------
/www/favicon/browserconfig.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 | #da532c
10 |
11 |
12 |
13 |
--------------------------------------------------------------------------------
/phpunit.xml:
--------------------------------------------------------------------------------
1 |
6 |
7 |
8 |
9 | tests
10 |
11 |
12 |
13 |
14 |
15 | app
16 |
17 |
18 |
19 |
--------------------------------------------------------------------------------
/db/migrations/20170627065214_abroad_events.php:
--------------------------------------------------------------------------------
1 | table('users')
10 | ->addColumn(
11 | 'abroad_events',
12 | Phinx\Db\Adapter\MysqlAdapter::PHINX_TYPE_BOOLEAN,
13 | ['after' => 'newsletter', 'default' => true]
14 | )
15 | ->update();
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/.travis.yml:
--------------------------------------------------------------------------------
1 | language: php
2 |
3 | php:
4 | - 7.1
5 |
6 | install:
7 | # install composer dependencies
8 | - composer install
9 |
10 | script:
11 | # check coding standard (defined in composer.json "scripts" section)
12 | - composer cs
13 | # check with phpstan (defined in composer.json "scripts" section)
14 | - composer phpstan
15 | # run tests
16 | - vendor/bin/phpunit
17 |
18 | # do not send success notifications, they have no value
19 | notifications:
20 | email:
21 | on_success: never
22 |
--------------------------------------------------------------------------------
/www/css/palette.less:
--------------------------------------------------------------------------------
1 | /* Palette generated by Material Palette - materialpalette.com/blue/light-green */
2 |
3 | @darkPrimaryColor: #1976D2;
4 | @darkishPrimaryColor: #2783DE;
5 | @primaryColor: #2196F3;
6 | @lightPrimaryColor: #BBDEFB;
7 | @textPrimaryColor: #FFFFFF;
8 | @accentColor: #8BC34A;
9 | @primaryTextColor: #212121;
10 | @secondaryTextColor: #727272;
11 | @dividerColor: #B6B6B6;
12 |
13 | @footerBgColor: #3e464c;
14 | @footerColor: #99979c;
15 |
16 | @redColor: #f44336;
--------------------------------------------------------------------------------
/db/migrations/20160309173103_nullable_columns.php:
--------------------------------------------------------------------------------
1 | table('events')
10 | ->changeColumn('description', 'text', ['null' => true])
11 | ->changeColumn('end', 'datetime', ['null' => true])
12 | ->changeColumn('image', 'string', ['limit' => 2083, 'comment' => 'url', 'null' => true])
13 | ->update();
14 | }
15 | }
16 |
--------------------------------------------------------------------------------
/app/Modules/Admin/Components/EventsTable/NotApprovedEventsTable.latte:
--------------------------------------------------------------------------------
1 |
2 |
3 | {="admin.notApprovedEventsTable.eventSeries"|translate}
4 | {="admin.notApprovedEventsTable.name"|translate}
5 | {="admin.notApprovedEventsTable.date"|translate}
6 | {="admin.notApprovedEventsTable.created"|translate}
7 |
8 |
9 |
10 |
--------------------------------------------------------------------------------
/app/Modules/Admin/Presenters/AbstractBasePresenter.php:
--------------------------------------------------------------------------------
1 | user->isLoggedIn() || ! $this->user->isInRole('admin')) {
15 | $this->redirect('Sign:in');
16 | }
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/app/Modules/Admin/Presenters/templates/Sources/create.latte:
--------------------------------------------------------------------------------
1 | {layout '../@layout.latte'}
2 |
3 | {block header}
4 | {if $presenter->getAction() === 'create'}
5 | {="admin.sources.create.header"|translate}
6 | {else}
7 | {="admin.sources.update.header"|translate}
8 | {/if}
9 | {/block}
10 |
11 | {block content}
12 |
13 |
14 |
15 |
16 | {control sourceForm}
17 |
18 |
19 |
20 |
21 | {/block}
22 |
--------------------------------------------------------------------------------
/app/web.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/log/web.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/temp/web.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/app/Modules/Core/Components/Form/Form.php:
--------------------------------------------------------------------------------
1 | getRenderer();
16 | $renderer->wrappers['label']['container'] = 'th class="th-label"';
17 | $renderer->wrappers['control']['container'] = 'td class="td-control"';
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/app/config/config.neon:
--------------------------------------------------------------------------------
1 | includes:
2 | - database.neon
3 | - factories.neon
4 | - services.neon
5 | - decorator.neon
6 | - parameters.neon
7 | - extensions/translation.neon
8 | - extensions/console.neon
9 | - extensions/facebook.neon
10 | - extensions/restful.neon
11 |
12 | php:
13 | date.timezone: Europe/Prague
14 |
15 |
16 | application:
17 | errorPresenter: Core:Error
18 | scanComposer: false
19 | scanDirs: false
20 | mapping:
21 | *: App\Modules\*\Presenters\*Presenter
22 |
23 | session:
24 | expiration: 365 days
25 | savePath: "%tempDir%/sessions"
26 |
27 | extensions:
28 | slackLogger: greeny\NetteSlackLogger\DI\SlackLoggerExtension
29 |
--------------------------------------------------------------------------------
/app/Modules/Admin/Model/SourceModel.php:
--------------------------------------------------------------------------------
1 | 1,
14 | 'twiceAWeek' => 3,
15 | 'weekly' => 7,
16 | 'fortnightly' => 14,
17 | 'monthly' => 30,
18 | 'quarterly' => 90,
19 | 'half-yearly' => 183,
20 | 'yearly' => 365,
21 | ];
22 |
23 | /**
24 | * @var string
25 | */
26 | protected const TABLE_NAME = 'sources';
27 | }
28 |
--------------------------------------------------------------------------------
/app/Modules/Admin/Presenters/ExceptionPresenter.php:
--------------------------------------------------------------------------------
1 | sendResponse(new FileResponse($file, $filename, 'text/html', false));
19 | }
20 | }
21 |
--------------------------------------------------------------------------------
/db/migrations/20160505142316_newsletter_table.php:
--------------------------------------------------------------------------------
1 | table('newsletters');
10 | $newsletter->addColumn('subject', 'string', array('limit' => 100))
11 | ->addColumn('from', 'string', array('limit' => 100))
12 | ->addColumn('intro_text', 'text')
13 | ->addColumn('outro_text', 'text')
14 | ->addColumn('author', 'string', array('limit' => 20))
15 | ->addColumn('created', 'timestamp', ['default' => 'CURRENT_TIMESTAMP'])
16 | ->create();
17 | }
18 | }
19 |
--------------------------------------------------------------------------------
/app/Modules/Front/Presenters/templates/Homepage/discover.latte:
--------------------------------------------------------------------------------
1 | {layout '../@layout.latte'}
2 |
3 | {block content}
4 |
5 |
6 |
7 | {control subscriptionTags}
8 |
9 |
10 |
13 | {control eventsList}
14 |
15 |
16 |
17 |
18 | {/block}
19 |
--------------------------------------------------------------------------------
/app/Modules/Core/Model/EventSources/AbstractEventSource.php:
--------------------------------------------------------------------------------
1 | getHost();
15 |
16 | return in_array($host, static::URLS, true);
17 | } catch (Throwable $throwable) {
18 | return false;
19 | }
20 | }
21 |
22 | /**
23 | * @return Event[]
24 | */
25 | abstract public function getEvents(string $source): array;
26 | }
27 |
--------------------------------------------------------------------------------
/app/config/templates/config.local.neon:
--------------------------------------------------------------------------------
1 | parameters:
2 |
3 | baseUrl: 'https://eventigo.cz'
4 |
5 | lastAccess:
6 | syncToDb: 5 minutes
7 |
8 | newsletter:
9 | sendNewsletterWithNoEvents: true
10 | defaultSubject: Akce na příští týden
11 | defaultAuthor:
12 | name: Filip
13 | email: filip@eventigo.cz
14 |
15 | # googleAnalyticsKey: UA-XXXX
16 |
17 | SendGrid:
18 | ApiKey: ''
19 |
20 | meetup:
21 | apiKey: ''
22 |
23 | database:
24 | host:
25 | name:
26 | username:
27 | password:
28 |
29 | slackLogger:
30 | enabled: false
31 | timeout: 60
32 | slackUrl: https://hooks.slack.com/services/XXXX
33 | logUrl: '%baseUrl%/admin/exception/?filename=__FILE__'
34 | defaults:
35 | name: EventigoApp
36 |
--------------------------------------------------------------------------------
/db/migrations/20170626060205_venue.php:
--------------------------------------------------------------------------------
1 | table('events')
10 | ->addColumn(
11 | 'venue',
12 | Phinx\Db\Adapter\MysqlAdapter::PHINX_TYPE_STRING,
13 | ['after' => 'end', 'null' => true])
14 | ->addColumn(
15 | 'country_id',
16 | Phinx\Db\Adapter\MysqlAdapter::PHINX_TYPE_STRING,
17 | ['after' => 'venue', 'limit' => 2, 'null' => true]
18 | )
19 | ->addForeignKey('country_id', 'countries', 'id')
20 | ->update();
21 | }
22 | }
23 |
--------------------------------------------------------------------------------
/app/Modules/Core/Model/EventTagModel.php:
--------------------------------------------------------------------------------
1 | getAll()->where('event_id', $eventId)->order('rate DESC')->fetchAll() as $eventTag) {
19 | $hashtags .= '#' . $eventTag->ref('tags', 'tag_id')->code . ' ';
20 | }
21 |
22 | return $hashtags;
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/www/.maintenance.php:
--------------------------------------------------------------------------------
1 |
7 |
8 |
9 |
10 |
11 |
12 |
17 |
18 | Site is temporarily down for maintenance
19 |
20 | We're Sorry
21 |
22 | The site is temporarily down for maintenance. Please try again in a few minutes.
23 |
24 | table('events_redirects')
10 | ->addColumn('event_id', 'integer', ['signed' => false])
11 | ->addColumn('user_id', 'integer', ['signed' => false, 'null' => true])
12 | ->addColumn('created', 'timestamp', ['default' => 'CURRENT_TIMESTAMP'])
13 | ->addForeignKey('event_id', 'events', 'id', ['update' => 'CASCADE', 'delete' => 'CASCADE'])
14 | ->addForeignKey('user_id', 'users', 'id', ['update' => 'CASCADE', 'delete' => 'CASCADE'])
15 | ->create();
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/app/Modules/Front/Components/EventsList/EventsList.php:
--------------------------------------------------------------------------------
1 | events = $events;
21 | }
22 |
23 | public function render(): void
24 | {
25 | $this->template->events = new EventsIterator($this->events);
26 | $this->template->render();
27 | }
28 | }
29 |
--------------------------------------------------------------------------------
/app/Modules/Admin/Presenters/templates/Sign/in.latte:
--------------------------------------------------------------------------------
1 | {layout '@layout.latte'}
2 |
3 | {block content}
4 | {form signInForm-form}
5 |
6 | {input email, class => form-control}
7 |
8 |
9 |
10 | {input password, class => form-control}
11 |
12 |
13 |
14 |
15 | {input signIn, class => 'btn btn-primary btn-block btn-flat'}
16 |
17 |
18 | {/form}
19 | {/block}
20 |
--------------------------------------------------------------------------------
/db/migrations/20160512205838_events_unique_index.php:
--------------------------------------------------------------------------------
1 | table('events');
15 | $events->changeColumn('name', 'string', array('limit' => 128))
16 | ->addIndex(array('name', 'start'), array('unique' => true))
17 | ->save();
18 | }
19 |
20 | public function down()
21 | {
22 | $events = $this->table('events');
23 | $events->changeColumn('name', 'string', array('limit' => 1024))
24 | ->removeIndex(array('name', 'start'))
25 | ->save();
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/db/migrations/20160504063746_facebook.php:
--------------------------------------------------------------------------------
1 | table('users')
10 | ->addColumn('facebook_id', 'string', ['null' => true, 'after' => 'email', 'limit' => 17])
11 | ->addColumn('facebook_token', 'string', ['null' => true, 'after' => 'token', 'limit' => 168])
12 | ->addColumn('firstname', 'string', ['null' => true, 'after' => 'email', 'limit' => 32])
13 | ->addColumn('fullname', 'string', ['null' => true, 'after' => 'firstname', 'limit' => 122])
14 | ->changeColumn('email', 'string', ['limit' => 254, 'null' => true])
15 | ->update();
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/db/migrations/20160324192410_users_events.php:
--------------------------------------------------------------------------------
1 | table('users_events', ['id' => false, 'primary_key' => ['user_id', 'event_id']])
10 | ->addColumn('user_id', 'integer', ['signed' => false])
11 | ->addColumn('event_id', 'integer', ['signed' => false])
12 | ->addColumn('created', 'timestamp', ['default' => 'CURRENT_TIMESTAMP'])
13 | ->addForeignKey('user_id', 'users', 'id', ['update' => 'CASCADE', 'delete' => 'CASCADE'])
14 | ->addForeignKey('event_id', 'events', 'id', ['update' => 'CASCADE', 'delete' => 'CASCADE'])
15 | ->create();
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/app/Modules/Admin/Components/EventsTable/js.latte:
--------------------------------------------------------------------------------
1 |
2 |
3 |
--------------------------------------------------------------------------------
/db/migrations/20160512150334_newsletter_content_limit.php:
--------------------------------------------------------------------------------
1 | table('users_newsletters');
13 | $users->changeColumn('content', 'text', array('limit' => \Phinx\Db\Adapter\MysqlAdapter::TEXT_MEDIUM))
14 | ->save();
15 | }
16 |
17 | /**
18 | * Migrate Down.
19 | */
20 | public function down()
21 | {
22 | $users = $this->table('users_newsletters');
23 | $users->changeColumn('content', 'text', array('limit' => \Phinx\Db\Adapter\MysqlAdapter::TEXT_REGULAR))
24 | ->save();
25 | }
26 | }
27 |
--------------------------------------------------------------------------------
/app/Modules/Core/Model/EventSources/Utils/EventSource.php:
--------------------------------------------------------------------------------
1 | getHost();
25 |
26 | return in_array($host, self::SOURCES, true);
27 | } catch (Throwable $throwable) {
28 | return false;
29 | }
30 | }
31 | }
32 |
--------------------------------------------------------------------------------
/app/Modules/Api/V1/Presenters/EventsPresenter.php:
--------------------------------------------------------------------------------
1 | eventApiService = $eventApiService;
20 | }
21 |
22 | public function actionRead(): void
23 | {
24 | $this->resource = $this->eventApiService->getEvents();
25 | $this->sendResource(IResource::JSON);
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/tests/ContainerFactory.php:
--------------------------------------------------------------------------------
1 | setDebugMode(true);
14 | $configurator->setTempDirectory(__DIR__ . '/../temp');
15 |
16 | $configurator->addConfig(__DIR__ . '/../app/config/config.neon');
17 | $localConfig = __DIR__ . '/../app/config/config.local.neon';
18 | if (file_exists($localConfig)) {
19 | $configurator->addConfig($localConfig);
20 | }
21 | $configurator->addConfig(__DIR__ . '/config/config.test.neon');
22 |
23 | return $configurator->createContainer();
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/app/config/factories.neon:
--------------------------------------------------------------------------------
1 | services:
2 | - App\Modules\Front\Components\Subscription\SubscriptionFactoryInterface
3 | - App\Modules\Front\Components\SubscriptionTags\SubscriptionTagsFactoryInterface
4 | - App\Modules\Front\Components\EventsList\EventsListFactoryInterface
5 | - App\Modules\Admin\Components\EventForm\EventFormFactoryInterface
6 | - App\Modules\Admin\Components\SignIn\SignInFormFactoryInterface
7 | - App\Modules\Front\Components\Tags\TagsFactoryInterface
8 | - App\Modules\Front\Components\Settings\SettingsFactoryInterface
9 | - App\Modules\Admin\Components\SourceForm\SourceFormFactoryInterface
10 | - App\Modules\Admin\Components\SourcesTable\SourcesTableFactoryInterface
11 | - App\Modules\Admin\Components\EventsTable\NotApprovedEventsTableFactoryInterface
12 | - App\Modules\Front\Components\Sign\SignInFactoryInterface
13 |
--------------------------------------------------------------------------------
/app/Modules/Core/Presenters/templates/Error/500.phtml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Server Error
5 |
6 |
12 |
13 |
14 |
Server Error
15 |
16 |
We're sorry! The server encountered an internal error and
17 | was unable to complete your request. Please try again later.
18 |
19 |
error 500
20 |
21 |
--------------------------------------------------------------------------------
/app/Modules/Core/Presenters/Error4xxPresenter.php:
--------------------------------------------------------------------------------
1 | getRequest()->isMethod(Request::FORWARD)) {
14 | $this->error();
15 | }
16 | }
17 |
18 | public function renderDefault(BadRequestException $exception): void
19 | {
20 | // load template 403.latte or 404.latte or ... 4xx.latte
21 | $file = __DIR__ . "/templates/Error/{$exception->getCode()}.latte";
22 | $this->template->setFile(is_file($file) ? $file : __DIR__ . '/templates/Error/4xx.latte');
23 | }
24 | }
25 |
--------------------------------------------------------------------------------
/db/migrations/20160327201056_sources.php:
--------------------------------------------------------------------------------
1 | table('sources')
10 | ->addColumn('name', 'string', ['limit' => 1024, 'null' => true])
11 | ->addColumn('url', 'string', ['limit' => 2083, 'null' => true])
12 | ->addColumn('event_series_id', 'integer', ['signed' => false, 'null' => true])
13 | ->addColumn('check_frequency', 'integer', ['default' => 1])
14 | ->addColumn('next_check', 'date')
15 | ->addColumn('created', 'timestamp', ['default' => 'CURRENT_TIMESTAMP'])
16 | ->addForeignKey('event_series_id', 'events_series', 'id', ['update' => 'CASCADE', 'delete' => 'CASCADE'])
17 | ->create();
18 | }
19 | }
20 |
--------------------------------------------------------------------------------
/www/web.config:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
--------------------------------------------------------------------------------
/www/css/style.css:
--------------------------------------------------------------------------------
1 | body {
2 | font-size: 15px;
3 | line-height: 1.6;
4 | color: #333;
5 | background: white;
6 | }
7 |
8 | h1 {
9 | color: #3484D2;
10 | }
11 |
12 | #ajax-spinner {
13 | margin: 15px 0 0 15px;
14 | padding: 13px;
15 | background: white url('../img/spinner.gif') no-repeat 50% 50%;
16 | font-size: 0;
17 | z-index: 123456;
18 | display: none;
19 | }
20 |
21 | div.flash {
22 | color: black;
23 | background: #FFF9D7;
24 | border: 1px solid #E2C822;
25 | padding: 1em;
26 | margin: 1em 0;
27 | }
28 |
29 | a[href^="#error:"] {
30 | background: red;
31 | color: white;
32 | }
33 |
34 | form th, form td {
35 | vertical-align: top;
36 | font-weight: normal;
37 | }
38 |
39 | form th {
40 | text-align: right;
41 | }
42 |
43 | form .required label {
44 | font-weight: bold;
45 | }
46 |
47 | form .error {
48 | color: #D00;
49 | font-weight: bold;
50 | }
51 |
--------------------------------------------------------------------------------
/app/Modules/Admin/Presenters/templates/Events/default.latte:
--------------------------------------------------------------------------------
1 | {layout '../@layout.latte'}
2 |
3 | {block header}
4 |
5 | {/block}
6 |
7 | {block content}
8 |
15 |
16 |
17 |
20 |
21 | {control notApprovedEventsTable}
22 |
23 |
24 | {/block}
25 |
26 |
27 | {block scripts}
28 | {control notApprovedEventsTable:js}
29 | {/block}
--------------------------------------------------------------------------------
/www/favicon/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "Eventigo.cz",
3 | "icons": [
4 | {
5 | "src": "\/favicon\/android-chrome-36x36.png",
6 | "sizes": "36x36",
7 | "type": "image\/png",
8 | "density": 0.75
9 | },
10 | {
11 | "src": "\/favicon\/android-chrome-48x48.png",
12 | "sizes": "48x48",
13 | "type": "image\/png",
14 | "density": 1
15 | },
16 | {
17 | "src": "\/favicon\/android-chrome-72x72.png",
18 | "sizes": "72x72",
19 | "type": "image\/png",
20 | "density": 1.5
21 | },
22 | {
23 | "src": "\/favicon\/android-chrome-96x96.png",
24 | "sizes": "96x96",
25 | "type": "image\/png",
26 | "density": 2
27 | },
28 | {
29 | "src": "\/favicon\/android-chrome-144x144.png",
30 | "sizes": "144x144",
31 | "type": "image\/png",
32 | "density": 3
33 | },
34 | {
35 | "src": "\/favicon\/android-chrome-192x192.png",
36 | "sizes": "192x192",
37 | "type": "image\/png",
38 | "density": 4
39 | }
40 | ]
41 | }
42 |
--------------------------------------------------------------------------------
/app/bootstrap.php:
--------------------------------------------------------------------------------
1 | setDebugMode(in_array(
9 | $_SERVER['HTTP_HOST'],
10 | ['localhost:8000', 'kuba.eventigo.cz', 'filip.eventigo.cz', 'eventigo.local', 'eventigo.local.cz'],
11 | true
12 | ));
13 | } else {
14 | $configurator->setDebugMode(true);
15 | }
16 |
17 | // Fix redirects to port 80 (https://forum.nette.org/cs/13896-openshift-redirect-z-https-pridava-port-80)
18 | if (! empty($_SERVER['HTTPS']) && $_SERVER['HTTPS'] !== 'off') {
19 | $_SERVER['SERVER_PORT'] = 443;
20 | }
21 |
22 | $configurator->enableDebugger(__DIR__ . '/../log');
23 |
24 | $configurator->setTempDirectory(__DIR__ . '/../temp');
25 |
26 | $configurator->addConfig(__DIR__ . '/config/config.neon');
27 | $configurator->addConfig(__DIR__ . '/config/config.local.neon');
28 |
29 | return $configurator->createContainer();
30 |
--------------------------------------------------------------------------------
/www/.htaccess:
--------------------------------------------------------------------------------
1 | # Apache configuration file (see httpd.apache.org/docs/current/mod/quickreference.html)
2 |
3 | # disable directory listing
4 |
5 | Options -Indexes
6 |
7 |
8 | # enable cool URL
9 |
10 | RewriteEngine On
11 | # RewriteBase /
12 |
13 | RewriteCond %{HTTP_HOST} ^www\.eventigo\.cz$ [NC]
14 | RewriteRule ^(.*)$ https://eventigo.cz/$1 [L,R=301]
15 |
16 | # prevents files starting with dot to be viewed by browser
17 | RewriteRule /\.|^\.(?!well-known/) - [F]
18 |
19 | # front controller
20 | RewriteCond %{REQUEST_FILENAME} !-f
21 | RewriteCond %{REQUEST_FILENAME} !-d
22 | RewriteRule !\.(pdf|js|ico|gif|jpg|png|css|rar|zip|tar\.gz|map)$ index.php [L]
23 |
24 |
25 | # enable gzip compression
26 |
27 |
28 | AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css application/javascript application/json application/xml image/svg+xml
29 |
30 |
31 |
--------------------------------------------------------------------------------
/phinx.yml.template:
--------------------------------------------------------------------------------
1 | paths:
2 | migrations: %%PHINX_CONFIG_DIR%%/db/migrations
3 | seeds: %%PHINX_CONFIG_DIR%%/db/seeds
4 |
5 | environments:
6 | default_migration_table: phinxlog
7 | default_database: development
8 |
9 | local:
10 | adapter: mysql
11 | host: localhost
12 | name: production_db
13 | user: root
14 | pass: ''
15 | port: 3306
16 | charset: utf8
17 |
18 | development:
19 | adapter: mysql
20 | host: localhost
21 | name: development_db
22 | user: root
23 | pass: ''
24 | port: 3306
25 | charset: utf8
26 |
27 | testing:
28 | adapter: mysql
29 | host: localhost
30 | name: testing_db
31 | user: root
32 | pass: ''
33 | port: 3306
34 | charset: utf8
35 |
36 | production:
37 | adapter: mysql
38 | host: localhost
39 | name: production_db
40 | user: root
41 | pass: ''
42 | port: 3306
43 | charset: utf8
44 |
--------------------------------------------------------------------------------
/app/Modules/Core/Model/UserTagModel.php:
--------------------------------------------------------------------------------
1 | getAll()
18 | ->select('tag.tag_group.name AS tagGroupName')
19 | ->select('tag.code')
20 | ->where('user_id', $userId)
21 | ->group('tag.tag_group_id, tag.id')
22 | ->fetchAssoc('tagGroupName[]=code');
23 | }
24 |
25 | /**
26 | * Get ids of user's subscribed tags.
27 | *
28 | * @return mixed[]
29 | */
30 | public function getUserTagIds(int $userId): array
31 | {
32 | return $this->getAll()
33 | ->select('tag_id')
34 | ->where('user_id', $userId)
35 | ->fetchAssoc('[]=tag_id');
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/app/Modules/Core/Utils/Collection.php:
--------------------------------------------------------------------------------
1 | id = $id;
27 | $this->name = $name;
28 | $this->code = $code;
29 | }
30 |
31 | public static function createFromRow(IRow $tagRow): Tag
32 | {
33 | return new self($tagRow['id'], $tagRow['name'], $tagRow['code']);
34 | }
35 |
36 | public function getId(): int
37 | {
38 | return $this->id;
39 | }
40 |
41 | public function getName(): string
42 | {
43 | return $this->name;
44 | }
45 |
46 | public function getCode(): string
47 | {
48 | return $this->code;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/app/Modules/Front/Presenters/templates/Profile/settings.latte:
--------------------------------------------------------------------------------
1 | {layout '../@layout.latte'}
2 |
3 | {block content}
4 |
5 | {* subscription *}
6 |
7 |
12 |
13 |
14 | {control settings}
15 |
16 |
17 |
18 |
19 | {* tags *}
20 |
30 |
31 | {/block}
32 |
--------------------------------------------------------------------------------
/db/migrations/20160402220653_tag_group.php:
--------------------------------------------------------------------------------
1 | table('tags_groups', ['id' => false, 'primary_key' => 'id'])
10 | ->addColumn('id', 'integer', ['signed' => false, 'identity' => true])
11 | ->addColumn('name', 'string', ['limit' => 32])
12 | ->addColumn('created', 'timestamp', ['default' => 'CURRENT_TIMESTAMP'])
13 | ->create();
14 |
15 | $this->table('tags')
16 | ->addColumn('tag_group_id', 'integer', ['signed' => false, 'null' => true, 'after' => 'code'])
17 | ->addForeignKey('tag_group_id', 'tags_groups', 'id', ['update' => 'CASCADE', 'delete' => 'SET_NULL'])
18 | ->update();
19 |
20 | $this->table('tags_groups')->insert([
21 | ['name' => 'development'],
22 | ['name' => 'business'],
23 | ['name' => 'marketing'],
24 | ['name' => 'others'],
25 | ])->update();
26 | }
27 | }
28 |
--------------------------------------------------------------------------------
/app/Modules/Newsletter/Model/UserNewsletterModel.php:
--------------------------------------------------------------------------------
1 | insert([
21 | 'user_id' => $userId,
22 | 'from' => $from,
23 | 'subject' => $subject,
24 | 'content' => $content,
25 | 'hash' => $hash ?? $this->generateUniqueHash(),
26 | ]);
27 | }
28 |
29 | public function generateUniqueHash(): string
30 | {
31 | do {
32 | $hash = Random::generate(32);
33 | } while ($this->getAll()->where(['hash' => $hash])->fetch());
34 |
35 | return $hash;
36 | }
37 | }
38 |
--------------------------------------------------------------------------------
/app/Modules/Admin/Components/DataTable/AbstractDataTable.php:
--------------------------------------------------------------------------------
1 | dataSource = $dataSource;
25 | }
26 |
27 | /**
28 | * @return mixed[]
29 | */
30 | abstract public function generateJson(): array;
31 |
32 | public function handleJson(): void
33 | {
34 | $json = $this->generateJson();
35 | $this->presenter->sendResponse(new JsonResponse($json));
36 | }
37 |
38 | public function getLang(): string
39 | {
40 | return '//cdn.datatables.net/plug-ins/1.10.11/i18n/Czech.json';
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/app/Modules/Core/Utils/Helper.php:
--------------------------------------------------------------------------------
1 | tag.
14 | */
15 | public static function utfToHtmlEntities(string $input): string
16 | {
17 | $emogrifier = new Emogrifier;
18 |
19 | try {
20 | $emogrifier->setHtml($input);
21 |
22 | return $emogrifier->emogrifyBodyContent();
23 | } catch (BadMethodCallException $e) {
24 | Debugger::log($e->getMessage());
25 |
26 | return $input;
27 | }
28 | }
29 |
30 | /**
31 | * Returns utm params from array of parameters.
32 | *
33 | * @param string[] $params
34 | * @return string[]
35 | */
36 | public static function extractUtmParameters(array $params): array
37 | {
38 | $utmParams = ['utm_source', 'utm_campaign', 'utm_medium'];
39 |
40 | return array_intersect_key($params, array_flip($utmParams));
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/phpstan.neon:
--------------------------------------------------------------------------------
1 | parameters:
2 | ignoreErrors:
3 | - '#::__construct\(\) does not call parent constructor from Nette\\Application\\UI\\Presenter#'
4 | - '#::__construct\(\) does not call parent constructor from Nette\\ComponentModel\\Component#'
5 | - '#Parameter \#2 \$count of method Kdyby\\Translation\\Translator::translate\(\) expects int\|null, Nette\\Utils\\Html|mixed|string|string\[\] given#'
6 | - '#Call to an undefined method SendGrid\\Client::mail\(\)#'
7 | - '#Access to undefined constant App\\Modules\\Core\\Model\\EventSources\\AbstractEventSource::URLS#'
8 | - '#Parameter \#1 \$eventTagRow of static method App\\Modules\\Core\\Model\\Entity\\EventTag::createFromRow\(\) expects Nette\\Database\\Table\\ActiveRow, Nette\\Database\\Table\\IRow given#'
9 | - '#Property Drahak\\Restful\\Application\\UI\\ResourcePresenter::\$resource \(Drahak\\Restful\\IResource\) does not accept App\\Modules\\Core\\Model\\Entity\\Event\[\]#'
10 | - '#Access to an undefined property Nette\\Database\\Table\\Selection::\$id#'
11 |
12 | includes:
13 | - vendor/phpstan/phpstan-nette/extension.neon
14 | - vendor/phpstan/phpstan-nette/rules.neon
15 |
--------------------------------------------------------------------------------
/app/Modules/Core/Model/AbstractIterator.php:
--------------------------------------------------------------------------------
1 | data = array_values($data);
26 | }
27 |
28 | public function current(): ?IRow
29 | {
30 | return $this->valid() ? $this->data[$this->index] : null;
31 | }
32 |
33 | public function next(): ?IRow
34 | {
35 | ++$this->index;
36 |
37 | return $this->current();
38 | }
39 |
40 | public function key(): int
41 | {
42 | return $this->index;
43 | }
44 |
45 | public function valid(): bool
46 | {
47 | return array_key_exists($this->index, $this->data);
48 | }
49 |
50 | public function rewind(): void
51 | {
52 | $this->index = 0;
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/db/migrations/20160322091004_users_events_series_and_organisers.php:
--------------------------------------------------------------------------------
1 | table('users_events_series', ['id' => false, 'primary_key' => ['user_id', 'event_series_id']])
11 | ->addColumn('user_id', 'integer', ['signed' => false])
12 | ->addColumn('event_series_id', 'integer', ['signed' => false])
13 | ->addForeignKey('user_id', 'users', 'id', ['update' => 'CASCADE', 'delete' => 'CASCADE'])
14 | ->addForeignKey('event_series_id', 'events_series', 'id', ['update' => 'CASCADE', 'delete' => 'CASCADE'])
15 | ->create();
16 |
17 | // Users follow organisers
18 | $this->table('users_organisers', ['id' => false, 'primary_key' => ['user_id', 'organiser_id']])
19 | ->addColumn('user_id', 'integer', ['signed' => false])
20 | ->addColumn('organiser_id', 'integer', ['signed' => false])
21 | ->addForeignKey('user_id', 'users', 'id', ['update' => 'CASCADE', 'delete' => 'CASCADE'])
22 | ->addForeignKey('organiser_id', 'organisers', 'id', ['update' => 'CASCADE', 'delete' => 'CASCADE'])
23 | ->create();
24 | }
25 | }
26 |
--------------------------------------------------------------------------------
/app/Modules/Front/Presenters/SignPresenter.php:
--------------------------------------------------------------------------------
1 | translator = $translator;
33 | }
34 |
35 | public function actionOut(): void
36 | {
37 | if (! $this->getUser()->isLoggedIn()) {
38 | $this->redirect('Homepage:');
39 | }
40 |
41 | $this->getUser()->logout(true);
42 |
43 | $this->flashMessage($this->translator->translate('front.sign.out', [
44 | 'bye' => self::BYE[array_rand(self::BYE)],
45 | ]));
46 | $this->redirect('Homepage:');
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/db/migrations/20160421173837_newsletter_refactoring.php:
--------------------------------------------------------------------------------
1 | table('users_newsletters')->drop();
10 | $this->table('newsletters')->drop();
11 | $this->table('newsletters_contents')->drop();
12 | $this->table('newsletters_layouts')->drop();
13 |
14 | $this->table('users_newsletters', ['id' => false, 'primary_key' => 'id'])
15 | ->addColumn('id', 'integer', ['signed' => false, 'identity' => true])
16 | ->addColumn('from', 'string', ['length' => 254])
17 | ->addColumn('subject', 'string', ['length' => 100])
18 | ->addColumn('content', 'text')
19 | ->addColumn('hash', 'string', ['length' => 32, 'null' => true])
20 | ->addColumn('user_id', 'integer', ['signed' => false])
21 | ->addColumn('sent', 'datetime', ['null' => true])
22 | ->addColumn('created', 'timestamp', ['default' => 'CURRENT_TIMESTAMP'])
23 | ->addForeignKey('user_id', 'users', 'id', ['update' => 'CASCADE', 'delete' => 'CASCADE'])
24 | ->create();
25 |
26 | $this->table('users')
27 | ->addColumn('newsletter', 'boolean', ['after' => 'token', 'default' => true])
28 | ->update();
29 | }
30 | }
31 |
--------------------------------------------------------------------------------
/app/Modules/Core/Model/Entity/EventTag.php:
--------------------------------------------------------------------------------
1 | event = $event;
27 | $this->tag = $tag;
28 | $this->rate = $rate;
29 | }
30 |
31 | public static function createFromRow(ActiveRow $eventTagRow): EventTag
32 | {
33 | return new self(
34 | Event::createFromRow($eventTagRow->ref('events', 'event_id')),
35 | Tag::createFromRow($eventTagRow->ref('tags', 'tag_id')),
36 | $eventTagRow['rate']
37 | );
38 | }
39 |
40 | public function getEvent(): Event
41 | {
42 | return $this->event;
43 | }
44 |
45 | public function getTag(): Tag
46 | {
47 | return $this->tag;
48 | }
49 |
50 | public function getRate(): int
51 | {
52 | return $this->rate;
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/app/config/services.neon:
--------------------------------------------------------------------------------
1 | services:
2 | router: App\Router\RouterFactory::createRouter
3 | - App\Modules\Core\Model\EventModel
4 | - App\Modules\Core\Model\TagModel
5 | - App\Modules\Core\Model\UserModel
6 | - App\Modules\Core\Model\UserTagModel
7 | - App\Modules\Newsletter\Model\NewsletterService
8 | - SendGrid(%SendGrid.ApiKey%)
9 | - App\Modules\Newsletter\Model\UserNewsletterModel
10 | - App\Modules\Newsletter\Model\NewsletterModel
11 | - App\Modules\Core\Model\EventTagModel
12 | - App\Modules\Admin\Model\EventService
13 | - App\Modules\Admin\Model\Authenticator
14 | - App\Modules\Core\Model\EventRedirectModel
15 | - App\Modules\Core\Model\TagGroupModel
16 | - App\Modules\Core\Model\EventSources\Facebook\FacebookEventSource
17 | - App\Modules\Core\Model\OrganiserModel
18 | - App\Modules\Core\Model\EventSeriesModel
19 | - App\Modules\Admin\Model\OrganiserService
20 | - App\Modules\Admin\Model\SourceModel
21 | - App\Modules\Admin\Model\SourceService
22 | - App\Modules\Email\Model\EmailService
23 | - App\Modules\Core\Model\EventSources\Srazy\SrazyEventSource
24 | -
25 | class: App\Modules\Core\Model\EventSources\Meetup\MeetupEventSource
26 | setup:
27 | - setApiKey(%meetup.apiKey%)
28 | - App\Modules\Api\V1\Model\EventApiService
29 | - App\Modules\Core\Model\CountryModel
30 |
--------------------------------------------------------------------------------
/app/Modules/Admin/Console/CrawlSourcesCommand.php:
--------------------------------------------------------------------------------
1 | sourceService = $sourceService;
20 | parent::__construct();
21 | }
22 |
23 | protected function configure(): void
24 | {
25 | $this->setName('admin:crawlSources')
26 | ->setDescription('Crawl events from sources');
27 | }
28 |
29 | protected function execute(InputInterface $input, OutputInterface $output): int
30 | {
31 | $addedEvents = $this->sourceService->crawlSources();
32 |
33 | if ($addedEvents) {
34 | $output->writeln($addedEvents . ' new events has been added to be approved');
35 | } else {
36 | $output->writeln('No new events has been added');
37 | }
38 |
39 | return 0;
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/app/Modules/Core/Presenters/ErrorPresenter.php:
--------------------------------------------------------------------------------
1 | logger = $logger;
23 | }
24 |
25 | public function run(Request $request): IResponse
26 | {
27 | $e = $request->getParameter('exception');
28 |
29 | if ($e instanceof BadRequestException) {
30 | // $this->logger->log("HTTP code {$e->getCode()}:
31 | // {$e->getMessage()} in {$e->getFile()}:{$e->getLine()}", 'access');
32 | return new ForwardResponse($request->setPresenterName('Core:Error4xx'));
33 | }
34 |
35 | $this->logger->log($e, ILogger::EXCEPTION);
36 |
37 | return new CallbackResponse(function (): void {
38 | require __DIR__ . '/templates/Error/500.phtml';
39 | });
40 | }
41 | }
42 |
--------------------------------------------------------------------------------
/www/favicon/safari-pinned-tab.svg:
--------------------------------------------------------------------------------
1 |
2 |
4 |
7 |
8 | Created by potrace 1.11, written by Peter Selinger 2001-2013
9 |
10 |
12 |
20 |
22 |
23 |
24 |
--------------------------------------------------------------------------------
/app/Modules/Admin/Presenters/templates/Events/create.latte:
--------------------------------------------------------------------------------
1 | {layout '../@layout.latte'}
2 |
3 | {block header}
4 | {if $presenter->getAction() === 'create'}
5 | {="admin.events.create.header"|translate}
6 | {elseif $presenter->getAction() === 'approve'}
7 | {="admin.events.approve.header"|translate}
8 | {else}
9 | {="admin.events.update.header"|translate}
10 | {/if}
11 | {/block}
12 |
13 | {block content}
14 |
15 |
16 | {control eventForm}
17 |
18 |
19 | {/block}
20 |
21 | {block scripts}
22 |
43 | {/block}
44 |
--------------------------------------------------------------------------------
/app/Modules/Newsletter/Console/CreateNewsletterCommand.php:
--------------------------------------------------------------------------------
1 | newsletterService = $newsletterService;
20 | parent::__construct();
21 | }
22 |
23 | protected function configure(): void
24 | {
25 | $this->setName('newsletters:create')
26 | ->setDescription('Creates default newsletter');
27 | }
28 |
29 | protected function execute(InputInterface $input, OutputInterface $output): int
30 | {
31 | $result = $this->newsletterService->createDefaultNewsletter();
32 | if ($result) {
33 | $output->writeln('New newsletter id: ' . $result . ' ');
34 |
35 | return 0;
36 | }
37 |
38 | $output->writeln('Could not create newsletter ');
39 |
40 | return 1;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/app/Modules/Admin/Console/GeneratePasswordCommand.php:
--------------------------------------------------------------------------------
1 | userModel = $userModel;
21 | parent::__construct();
22 | }
23 |
24 | protected function configure(): void
25 | {
26 | $this->setName('admin:generatePassword')
27 | ->setDescription('Generate admin password')
28 | ->addArgument(
29 | 'password',
30 | InputArgument::REQUIRED,
31 | 'Raw password'
32 | );
33 | }
34 |
35 | protected function execute(InputInterface $input, OutputInterface $output): int
36 | {
37 | $password = (string) $input->getArgument('password');
38 |
39 | $hash = $this->userModel->hashAndEncrypt($password);
40 |
41 | $output->writeln($hash);
42 |
43 | return 0;
44 | }
45 | }
46 |
--------------------------------------------------------------------------------
/www/css/admin.css:
--------------------------------------------------------------------------------
1 | #main {
2 | display: block;
3 | }
4 | #main form th.th-label {
5 | padding: 0.6rem 1rem 1rem 0rem;
6 | text-align: right;
7 | }
8 | #main form td.td-control input,
9 | #main form td.td-control textarea {
10 | width: 100%;
11 | }
12 | #main form td.td-control input[type=checkbox] {
13 | width: inherit;
14 | margin-right: 1rem;
15 | }
16 | #main form td.td-control textarea {
17 | border: 1px solid #ccc;
18 | }
19 | #main form td.td-control .select2-container--default .select2-selection--single {
20 | border: 1px solid #ccc;
21 | }
22 | #main form fieldset {
23 | border: none;
24 | display: inline-block;
25 | vertical-align: top;
26 | }
27 | #main .pure-box {
28 | margin-bottom: 20px;
29 | }
30 | #frm-signInForm-form table {
31 | margin: auto;
32 | }
33 | .info-box-content .info-box-text.inline,
34 | .info-box-content .info-box-number.inline {
35 | text-transform: inherit;
36 | display: inline;
37 | }
38 | .login-logo,
39 | .logo-lg {
40 | font-weight: 400;
41 | font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
42 | -webkit-font-smoothing: antialiased;
43 | }
44 | table.table a.btn {
45 | margin-left: 0.2rem;
46 | margin-right: 0.2rem;
47 | }
48 | #image-preview {
49 | width: 360px;
50 | height: 130px;
51 | }
52 | #image-preview img {
53 | object-fit: cover;
54 | width: 100%;
55 | height: 100%;
56 | }
57 |
--------------------------------------------------------------------------------
/app/Modules/Front/Presenters/RedirectPresenter.php:
--------------------------------------------------------------------------------
1 | eventRedirectModel = $eventRedirectModel;
25 | $this->eventModel = $eventModel;
26 | }
27 |
28 | public function renderDefault(string $url): void
29 | {
30 | // Find event with same url
31 | $events = $this->eventModel->getAll()->where('origin_url', $url)->fetchAll();
32 |
33 | foreach ($events as $event) {
34 | // Count the redirect
35 | $this->eventRedirectModel->insert([
36 | 'event_id' => $event->id,
37 | 'user_id' => $this->getUser()->isLoggedIn() ? $this->getUser()->getId() : null,
38 | ]);
39 | }
40 |
41 | $this->redirectUrl(new Url($url));
42 | }
43 | }
44 |
--------------------------------------------------------------------------------
/www/css/admin.less:
--------------------------------------------------------------------------------
1 | @inputBorder: #ccc;
2 |
3 | #main {
4 | display: block;
5 |
6 | form {
7 | th.th-label {
8 | padding: 0.6rem 1rem 1rem 0rem;
9 | text-align: right;
10 | }
11 | td.td-control {
12 | input, textarea {
13 | width: 100%;
14 | }
15 | input[type=checkbox] {
16 | width: inherit;
17 | margin-right: 1rem;
18 | }
19 | textarea {
20 | border: 1px solid @inputBorder;
21 | }
22 | .select2-container--default .select2-selection--single {
23 | border: 1px solid @inputBorder;
24 | }
25 | }
26 |
27 | fieldset {
28 | border: none;
29 | display: inline-block;
30 | vertical-align: top;
31 | }
32 | }
33 |
34 | .pure-box {
35 | margin-bottom: 20px;
36 | }
37 | }
38 |
39 | #frm-signInForm-form table {
40 | margin: auto;
41 | }
42 |
43 | .info-box-content {
44 | .info-box-text.inline,
45 | .info-box-number.inline {
46 | text-transform: inherit;
47 | display: inline;
48 | }
49 | }
50 |
51 | .login-logo,
52 | .logo-lg {
53 | font-weight: 400;
54 | font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
55 | -webkit-font-smoothing: antialiased;
56 | }
57 |
58 | table.table {
59 | a.btn {
60 | margin-left: 0.2rem;
61 | margin-right: 0.2rem;
62 | }
63 | }
64 |
65 | #image-preview {
66 | width: 360px;
67 | height: 130px;
68 | img {
69 | object-fit: cover;
70 | width: 100%;
71 | height: 100%;
72 | }
73 | }
--------------------------------------------------------------------------------
/app/Modules/Newsletter/Model/NewsletterModel.php:
--------------------------------------------------------------------------------
1 | getAll()
27 | ->order('created DESC')
28 | ->limit(1);
29 |
30 | if ($newsletter->count() !== 0) {
31 | return $newsletter->fetch()->toArray();
32 | }
33 |
34 | throw new RuntimeException('No newsletters found');
35 | }
36 |
37 | public function createNewsletter(Newsletter $newsletter): IRow
38 | {
39 | return $this->insert([
40 | 'subject' => $newsletter->getSubject(),
41 | 'from' => $newsletter->getFrom(),
42 | 'intro_text' => $newsletter->getIntroText(),
43 | 'outro_text' => $newsletter->getOutroText(),
44 | 'author' => $newsletter->getAuthor(),
45 | ]);
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/app/Modules/Core/Components/AbstractBaseControl.php:
--------------------------------------------------------------------------------
1 | template->render();
21 | }
22 |
23 | protected function createTemplate(): Template
24 | {
25 | /** @var Template $template */
26 | $template = parent::createTemplate();
27 |
28 | if ($file = $this->getTemplateDefaultFile()) {
29 | $template->setFile($file);
30 | }
31 |
32 | $template->addFilter('datetime', function (NetteDateTime $a, ?NetteDateTime $b = null) {
33 | DateTime::setTranslator($this->translator);
34 |
35 | return DateTime::eventsDatetimeFilter($a, $b);
36 | });
37 |
38 | return $template;
39 | }
40 |
41 | /**
42 | * Derives template path from class name.
43 | */
44 | protected function getTemplateDefaultFile(): ?string
45 | {
46 | $refl = $this->getReflection();
47 | $file = dirname($refl->getFileName()) . '/' . $refl->getShortName() . '.latte';
48 |
49 | return file_exists($file) ? $file : null;
50 | }
51 | }
52 |
--------------------------------------------------------------------------------
/app/Modules/Admin/Presenters/DashboardPresenter.php:
--------------------------------------------------------------------------------
1 | eventModel = $eventModel;
18 | }
19 |
20 | public function actionDefault(): void
21 | {
22 | $this->template->approvedEventsCounts = $this->eventModel->getAll()
23 | ->select('COUNT(*) AS all')
24 | ->select('COUNT(IF(MONTH(start) = MONTH(?), 1, NULL)) AS thisMonth', $datetime = new DateTime)
25 | ->select('COUNT(IF(MONTH(start) = MONTH(?), 1, NULL)) AS nextMonth', $datetime->modifyClone('+1 MONTH'))
26 | ->where('start >= ?', $datetime)
27 | ->where('state = ?', EventModel::STATE_APPROVED)
28 | ->fetch();
29 |
30 | $this->template->notApprovedEventsCounts = $this->eventModel->getAll()
31 | ->select('COUNT(*) AS all')
32 | ->select('COUNT(IF(MONTH(start) = MONTH(?), 1, NULL)) AS thisMonth', $datetime = new DateTime)
33 | ->select('COUNT(IF(MONTH(start) = MONTH(?), 1, NULL)) AS nextMonth', $datetime->modifyClone('+1 MONTH'))
34 | ->where('start >= ?', $datetime)
35 | ->where('state = ?', EventModel::STATE_NOT_APPROVED)
36 | ->fetch();
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/app/Modules/Newsletter/Console/SendNewslettersCommand.php:
--------------------------------------------------------------------------------
1 | newsletterService = $newsletterService;
26 | $this->userNewsletterModel = $userNewsletterModel;
27 | parent::__construct();
28 | }
29 |
30 | protected function configure(): void
31 | {
32 | $this->setName('newsletters:send')
33 | ->setDescription('Send newsletters');
34 | }
35 |
36 | protected function execute(InputInterface $input, OutputInterface $output): int
37 | {
38 | $usersNewslettersIds = $this->userNewsletterModel->getAll()
39 | ->where('sent', null)
40 | ->fetchPairs(null, 'id');
41 |
42 | $this->newsletterService->sendNewsletters($usersNewslettersIds);
43 |
44 | $output->writeln(count($usersNewslettersIds) . ' newsletters have been sent');
45 |
46 | return 0;
47 | }
48 | }
49 |
--------------------------------------------------------------------------------
/app/Modules/Email/Model/Entity/Email.php:
--------------------------------------------------------------------------------
1 | from;
34 | }
35 |
36 | public function setFrom(string $from): self
37 | {
38 | $this->from = $from;
39 |
40 | return $this;
41 | }
42 |
43 | public function getTo(): string
44 | {
45 | return $this->to;
46 | }
47 |
48 | public function setTo(string $to): self
49 | {
50 | $this->to = $to;
51 |
52 | return $this;
53 | }
54 |
55 | public function getSubject(): string
56 | {
57 | return $this->subject;
58 | }
59 |
60 | public function setSubject(string $subject): self
61 | {
62 | $this->subject = $subject;
63 |
64 | return $this;
65 | }
66 |
67 | public function getBody(): string
68 | {
69 | return $this->body;
70 | }
71 |
72 | public function setBody(string $body): self
73 | {
74 | $this->body = $body;
75 |
76 | return $this;
77 | }
78 | }
79 |
--------------------------------------------------------------------------------
/app/Modules/Email/Presenters/EmailPresenter.php:
--------------------------------------------------------------------------------
1 | emailService = $emailService;
34 | }
35 |
36 | /**
37 | * Renders login email with provided user token.
38 | *
39 | * @throws \InvalidArgumentException
40 | * @throws \Nette\Application\UI\InvalidLinkException
41 | * @throws \Nette\InvalidArgumentException
42 | */
43 | public function renderLogin(string $token): void
44 | {
45 | /** @var ITemplate|Template $template */
46 | $template = $this->getTemplate();
47 | $template->getLatte()->setLoader(new StringLoader); // @todo: what is this for?
48 | $template->setFile($this->emailService->renderLoginEmail($token));
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/app/Modules/Newsletter/Console/RenderNewslettersCommand.php:
--------------------------------------------------------------------------------
1 | newsletterService = $newsletterService;
26 | $this->userModel = $userModel;
27 | parent::__construct();
28 | }
29 |
30 | protected function configure(): void
31 | {
32 | $this->setName('newsletters:render');
33 | $this->setDescription('Render users newsletters prepared to send');
34 | }
35 |
36 | protected function execute(InputInterface $input, OutputInterface $output): int
37 | {
38 | $users = $this->userModel->getAll()
39 | ->where('newsletter', true)
40 | ->fetchAll();
41 |
42 | $createdCount = 0;
43 |
44 | foreach ($users as $user) {
45 | $this->newsletterService->createUserNewsletter($user->getPrimary());
46 | ++$createdCount;
47 | }
48 |
49 | $output->writeln($createdCount . ' newsletters have been created');
50 |
51 | return 0;
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/app/Modules/Core/Model/AbstractBaseModel.php:
--------------------------------------------------------------------------------
1 | database = $database;
24 | }
25 |
26 | public function getAll(): Selection
27 | {
28 | return $this->database->table($this::TABLE_NAME);
29 | }
30 |
31 | /**
32 | * Inserts row in a table.
33 | * @param mixed[]|\Traversable|Selection array($column => $value)|\Traversable|Selection for INSERT ... SELECT
34 | * @return IRow|int|bool Returns IRow or number of affected rows for Selection or table without primary key
35 | */
36 | public function insert(iterable $data)
37 | {
38 | return $this->getAll()
39 | ->insert($data);
40 | }
41 |
42 | /**
43 | * Updates all rows in result set.
44 | * Joins in UPDATE are supported only in MySQL.
45 | * @param mixed[] ($column => $value)
46 | * @return int number of affected rows
47 | */
48 | public function update(iterable $data): int
49 | {
50 | return $this->getAll()
51 | ->update($data);
52 | }
53 |
54 | /**
55 | * @param mixed[] $data
56 | */
57 | public function delete(array $data): void
58 | {
59 | $this->getAll()->where($data)->delete();
60 | }
61 | }
62 |
--------------------------------------------------------------------------------
/app/Modules/Core/Utils/Filters.php:
--------------------------------------------------------------------------------
1 | fullname ?: $identity->email ?: self::$translator->translate('front.nav.user');
42 | }
43 |
44 | /**
45 | * Inline filter used for CSS inline and UTF8 to HTML entities conversion (email clients compatibility).
46 | */
47 | public static function inline(string $s, bool $stripTags = true): string
48 | {
49 | $output = Helper::utfToHtmlEntities($s);
50 | if ($stripTags) {
51 | $output = strip_tags($output);
52 | }
53 |
54 | return $output;
55 | }
56 | }
57 |
--------------------------------------------------------------------------------
/app/Modules/Email/Model/Entity/BasicEmail.php:
--------------------------------------------------------------------------------
1 | introText;
36 | }
37 |
38 | public function setIntroText(string $introText): BasicEmail
39 | {
40 | $this->introText = $introText;
41 |
42 | return $this;
43 | }
44 |
45 | public function getButtonText(): string
46 | {
47 | return $this->buttonText;
48 | }
49 |
50 | public function setButtonText(string $buttonText): self
51 | {
52 | $this->buttonText = $buttonText;
53 |
54 | return $this;
55 | }
56 |
57 | public function getButtonUrl(): Url
58 | {
59 | return $this->buttonUrl;
60 | }
61 |
62 | public function setButtonUrl(Url $buttonUrl): self
63 | {
64 | $this->buttonUrl = $buttonUrl;
65 |
66 | return $this;
67 | }
68 |
69 | public function getFooterText(): string
70 | {
71 | return $this->footerText;
72 | }
73 |
74 | public function setFooterText(string $footerText): BasicEmail
75 | {
76 | $this->footerText = $footerText;
77 |
78 | return $this;
79 | }
80 | }
81 |
--------------------------------------------------------------------------------
/db/migrations/20160318171343_organisers.php:
--------------------------------------------------------------------------------
1 | table('organisers', ['id' => false, 'primary_key' => 'id'])
11 | ->addColumn('id', 'integer', ['signed' => false, 'identity' => true])
12 | ->addColumn('name', 'string', ['limit' => 255])
13 | ->addColumn('url', 'string', ['limit' => 2083, 'null' => true])
14 | ->addColumn('icon', 'string', ['limit' => 2083, 'null' => true])
15 | ->addColumn('image', 'string', ['limit' => 2083, 'null' => true])
16 | ->addColumn('updated', 'timestamp', ['null' => true, 'default' => null, 'update' => 'CURRENT_TIMESTAMP'])
17 | ->addColumn('created', 'timestamp', ['default' => 'CURRENT_TIMESTAMP'])
18 | ->addIndex('name')
19 | ->create();
20 |
21 | // Create series
22 | $this->table('events_series', ['id' => false, 'primary_key' => 'id'])
23 | ->addColumn('id', 'integer', ['signed' => false, 'identity' => true])
24 | ->addColumn('name', 'string', ['limit' => 255, 'null' => true])
25 | ->addColumn('url', 'string', ['limit' => 2083, 'null' => true])
26 | ->addColumn('icon', 'string', ['limit' => 2083, 'null' => true])
27 | ->addColumn('image', 'string', ['limit' => 2083, 'null' => true])
28 | ->addColumn('organiser_id', 'integer', ['signed' => false])
29 | ->addForeignKey('organiser_id', 'organisers', 'id', ['update' => 'CASCADE'])
30 | ->create();
31 |
32 | // Add foreign key for events
33 | $this->table('events')
34 | ->addColumn('event_series_id', 'integer', ['after' => 'rate', 'null' => true, 'signed' => false])
35 | ->addForeignKey('event_series_id', 'events_series', 'id', ['update' => 'CASCADE'])
36 | ->update();
37 | }
38 | }
39 |
--------------------------------------------------------------------------------
/app/Modules/Admin/Components/SignIn/SignInForm.php:
--------------------------------------------------------------------------------
1 | render();
25 | }
26 |
27 | public function processForm(Form $form): void
28 | {
29 | $values = $form->getValues();
30 |
31 | try {
32 | $this->getPresenter()->getUser()->login(UserModel::ADMIN_LOGIN, $values->email, $values->password);
33 | } catch (AuthenticationException $e) {
34 | $this->onIncorrectLogIn();
35 | }
36 |
37 | $this->onLoggedIn();
38 | }
39 |
40 | protected function createComponentForm(): Form
41 | {
42 | $form = new Form;
43 | $form->setTranslator($this->translator->domain('admin.signInForm'));
44 |
45 | $form->addText('email', 'email')
46 | ->setAttribute('placeholder', 'email.placeholder')
47 | ->setRequired('email.required');
48 | $form->addPassword('password', 'password')
49 | ->setAttribute('placeholder', 'password.placeholder')
50 | ->setRequired('password.required');
51 |
52 | $form->addSubmit('signIn', 'signIn')->setAttribute('class', 'btn btn-success');
53 | $form->onSuccess[] = [$this, 'processForm'];
54 |
55 | return $form;
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/app/Modules/Admin/Presenters/templates/Sources/default.latte:
--------------------------------------------------------------------------------
1 | {layout '../@layout.latte'}
2 |
3 | {block header}
4 |
5 | {/block}
6 |
7 | {block content}
8 |
11 |
12 |
13 |
16 |
17 |
18 |
19 | {="admin.sources.default.table.name"|translate}
20 | {="admin.sources.default.table.nextCheck"|translate}
21 |
22 |
23 |
24 |
25 |
26 |
27 | {/block}
28 |
29 | {block scripts}
30 |
31 |
32 |
48 | {/block}
--------------------------------------------------------------------------------
/app/Router/RouterFactory.php:
--------------------------------------------------------------------------------
1 | /[/]', 'Dashboard:default');
19 | $router[] = $adminRouter;
20 |
21 | // Nesletter
22 | $newsletterRouter = new RouteList('Newsletter');
23 | $newsletterRouter[] = new Route('newsletter/', 'Newsletter:default');
24 | $newsletterRouter[] = new Route('newsletter/dynamic/', 'Newsletter:dynamic');
25 | $newsletterRouter[] = new Route('newsletter//', 'Newsletter:default');
26 | $router[] = $newsletterRouter;
27 |
28 | // Email
29 | $emailRouter = new RouteList('Email');
30 | $emailRouter[] = new Route('email/login', 'Email:login');
31 | $router[] = $emailRouter;
32 |
33 | // Api
34 | $router[] = new CrudRoute('api/v1/events', [
35 | 'module' => 'Api:V1',
36 | 'presenter' => 'Events',
37 | ]);
38 |
39 | // Front
40 | $router[] = new Route('profile/settings/', 'Front:Profile:settings');
41 | $router[] = new Route('discover/?', 'Front:Homepage:discover');
42 | $router[] = new Route('/[/]', [
43 | 'module' => 'Front',
44 | 'presenter' => 'Homepage',
45 | 'action' => 'default',
46 | ]);
47 |
48 | return $router;
49 | }
50 | }
51 |
--------------------------------------------------------------------------------
/composer.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "eventigo/eventigo-web",
3 | "require": {
4 | "php": "^7.1",
5 | "nette/application": "~2.4",
6 | "nette/bootstrap": "~2.4",
7 | "nette/caching": "~2.4",
8 | "nette/database": "~2.4",
9 | "nette/di": "~2.4",
10 | "nette/finder": "~2.4",
11 | "nette/forms": "~2.4",
12 | "nette/http": "~2.4",
13 | "nette/mail": "~2.4",
14 | "nette/robot-loader": "^2.4|^3.0",
15 | "nette/security": "~2.4",
16 | "nette/utils": "~2.4",
17 | "latte/latte": "~2.4",
18 | "tracy/tracy": "~2.4",
19 | "kdyby/translation": "~2.2",
20 | "sendgrid/sendgrid": "^5.5",
21 | "kdyby/facebook": "2.0.x-dev",
22 | "pelago/emogrifier": "@dev",
23 | "greeny/nette-slack-logger": "^1.2",
24 | "webuni/srazy-api-client": "1.0.x-dev",
25 | "dms/meetup-api-client": "2.0.x-dev",
26 | "drahak/restful": "@dev",
27 | "contributte/console": "^0.1"
28 | },
29 | "require-dev": {
30 | "phpunit/phpunit": "^6.4",
31 | "robmorgan/phinx": "^0.7",
32 | "phpstan/phpstan": "^0.8",
33 | "phpstan/phpstan-nette": "^0.8",
34 | "symplify/easy-coding-standard": "^2.5.5"
35 | },
36 | "autoload": {
37 | "psr-4": {
38 | "App\\": "app"
39 | }
40 | },
41 | "autoload-dev": {
42 | "psr-4": {
43 | "App\\Tests\\": "tests",
44 | "PHP_CodeSniffer\\": "vendor/squizlabs/php_codesniffer/src"
45 | }
46 | },
47 | "scripts": {
48 | "complete-check": ["phpunit", "@cs", "@phpstan"],
49 | "cs": "vendor/bin/ecs check app",
50 | "fs": "vendor/bin/ecs check app --fix",
51 | "phpstan": "vendor/bin/phpstan analyse app --level 5 -c phpstan.neon"
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/app/Modules/Core/Model/EventSources/Srazy/SrazyEventSource.php:
--------------------------------------------------------------------------------
1 | series()->get($series)->getEvents();
29 |
30 | $events = [];
31 | foreach ($srazyEvents as $event) {
32 | $start = $event->getStart() ? DateTime::from($event->getStart()) : null;
33 | if ($start && $start > new DateTime) {
34 | $events[] = new Event(
35 | null,
36 | $event->getName() ?? '',
37 | $event->getDescription() ?? '',
38 | $event->getUri(),
39 | $start,
40 | $event->getEnd() ? DateTime::from($event->getEnd()) : null,
41 | null,
42 | null,
43 | null, // TODO $e->setImage() Get image from community page
44 | Event::calculateRateByAttendeesCount(
45 | count($event->getConfirmedAttendees()) + count($event->getUnconfirmedAttendees())
46 | )
47 | );
48 | }
49 | }
50 |
51 | return $events;
52 | }
53 | }
54 |
--------------------------------------------------------------------------------
/tests/Modules/Newsletter/Model/NewsletterServiceTest.php:
--------------------------------------------------------------------------------
1 | create();
29 | $this->newsletterService = $container->getByType(NewsletterService::class);
30 | $this->linkGenerator = $container->getByType(LinkGenerator::class);
31 |
32 | $this->baseUrl = $container->getParameters()['baseUrl'];
33 | }
34 |
35 | public function testRedirectLinks()
36 | {
37 | $this->assertSame(
38 | $this->baseUrl . '/redirect/?url=someUrl',
39 | $this->linkGenerator->link('Front:Redirect:', ['someUrl'])
40 | );
41 | }
42 |
43 | public function testPrepareLinks()
44 | {
45 | $templateData = [];
46 | $templateData = $this->newsletterService->prepareLinks(
47 | $templateData, 'userToken', 'baseUrl', 'newsletterHash'
48 | );
49 |
50 | $this->assertSame([
51 | 'updatePreferencesUrl' => $this->baseUrl . '/profile/settings/userToken?utm_campaign=newsletterButton&utm_source=newsletter&utm_medium=email',
52 | 'feedUrl' => $this->baseUrl . '/?token=userToken&utm_campaign=newsletterButton&utm_source=newsletter&utm_medium=email',
53 | 'unsubscribeUrl' => 'baseUrl/newsletter/unsubscribe/newsletterHash'
54 | ], $templateData);
55 | }
56 | }
--------------------------------------------------------------------------------
/app/Modules/Admin/Presenters/SignPresenter.php:
--------------------------------------------------------------------------------
1 | signInFormFactory = $signInFormFactory;
25 | $this->translator = $translator;
26 | }
27 |
28 | public function actionIn(): void
29 | {
30 | if ($this->user->isLoggedIn() && $this->user->isInRole('admin')) {
31 | $this->redirect('Dashboard:');
32 | }
33 | }
34 |
35 | public function actionOut(): void
36 | {
37 | if (! $this->user->isLoggedIn()) {
38 | $this->redirect('in');
39 | }
40 |
41 | if ($this->user->isInRole('admin')) {
42 | $this->user->logout(true);
43 | }
44 |
45 | $this->redirect('in');
46 | }
47 |
48 | protected function createComponentSignInForm(): SignInForm
49 | {
50 | $control = $this->signInFormFactory->create();
51 |
52 | $control->onLoggedIn[] = function (): void {
53 | $this->redirect('Dashboard:');
54 | };
55 |
56 | $control->onIncorrectLogIn[] = function (): void {
57 | $this->flashMessage($this->translator->translate('admin.signInForm.incorrectLogIn'), 'danger');
58 | $this->redirect('this');
59 | };
60 |
61 | return $control;
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/app/Modules/Front/Model/EventsIterator.php:
--------------------------------------------------------------------------------
1 | thisWeek && $this->current()->thisWeek) {
33 | return $this->thisWeek = true;
34 | }
35 |
36 | return false;
37 | }
38 |
39 | public function drawThisMonthTitle(): bool
40 | {
41 | if (! $this->thisMonth && $this->current()->thisMonth && ! $this->current()->thisWeek) {
42 | return $this->thisMonth = true;
43 | }
44 |
45 | return false;
46 | }
47 |
48 | public function drawNextMonthTitle(): bool
49 | {
50 | if (! $this->nextMonth && $this->current()->nextMonth
51 | && ! $this->current()->thisWeek && ! $this->current()->thisMonth
52 | ) {
53 | return $this->nextMonth = true;
54 | }
55 |
56 | return false;
57 | }
58 |
59 | public function drawUpcomingTitle(): bool
60 | {
61 | if (! $this->upcoming && ! $this->current()->thisWeek
62 | && ! $this->current()->thisMonth && ! $this->current()->nextMonth
63 | ) {
64 | return $this->upcoming = true;
65 | }
66 |
67 | return false;
68 | }
69 |
70 | public function count(): int
71 | {
72 | return count($this->data);
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/app/Modules/Front/Components/SubscriptionTags/SubscriptionTags.latte:
--------------------------------------------------------------------------------
1 | {form form, class => ajax}
2 |
3 | {="front.subscription.form.email.label"|translate|noescape}
4 | {input email}
5 | {input real_subscribe}
6 | {input subscribe, class => ['btn','btn-success']}
7 |
8 |
11 |
41 | {/form}
42 |
--------------------------------------------------------------------------------
/app/Modules/Core/Model/TagModel.php:
--------------------------------------------------------------------------------
1 | getAll()
20 | ->select('tags.code')
21 | ->select('tags.name')
22 | ->select('COUNT(:events_tags.event_id) AS eventsCount')
23 | ->select('tag_group_id')
24 | ->select('tag_group.name AS tagGroupName')
25 | ->select('tags.id')
26 | ->where(':events_tags.event.start >= NOW()');
27 |
28 | if (! $showAbroad) {
29 | $selection
30 | ->where([':events_tags.event.country_id = ? OR :events_tags.event.country_id IS NULL' => 'CZ']);
31 | }
32 |
33 | return $selection->group('tag_group_id, tags.id')
34 | ->order('tag_group_id')
35 | ->order('eventsCount DESC')
36 | ->order('tags.name');
37 | }
38 |
39 | /**
40 | * Get tags with the most upcoming events and all others.
41 | */
42 | public function getAllByMostEvents(): Selection
43 | {
44 | return $this->getAll()
45 | ->select('tags.code')
46 | ->select('tags.name')
47 | ->select('COUNT(IF(:events_tags.event_id IS NOT NULL AND :events_tags.event.start >= NOW(),'
48 | . ' TRUE, NULL)) AS eventsCount')
49 | ->select('tag_group_id')
50 | ->select('tag_group.name AS tagGroupName')
51 | ->select('tags.id')
52 | ->group('tag_group_id, tags.id')
53 | ->order('tag_group_id')
54 | ->order('eventsCount DESC')
55 | ->order('tags.name');
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/app/Modules/Admin/Presenters/templates/Sign/@layout.latte:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | AdminLTE 2 | Log in
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
21 |
22 |
23 |
24 |
25 | eventigo
26 |
27 |
28 | {include content}
29 |
30 |
31 |
32 |
33 |
34 |
35 |
44 |
45 |
46 |
--------------------------------------------------------------------------------
/app/Modules/Front/Components/Sign/SignIn.php:
--------------------------------------------------------------------------------
1 | userModel = $userModel;
36 | $this->emailService = $emailService;
37 | }
38 |
39 | public function processForm(Form $form, ArrayHash $values): void
40 | {
41 | $user = $this->userModel->getUserByEmail($values->email);
42 |
43 | if (! $user) {
44 | $user = $this->userModel->subscribe($values->email);
45 | }
46 |
47 | $this->emailService->sendLogin($values->email, $user->token);
48 | $this->onSuccess($values->email);
49 | }
50 |
51 | protected function createComponentForm(): Form
52 | {
53 | $form = new Form;
54 | $form->setTranslator($this->translator->domain('front.signIn.form'));
55 |
56 | $form->addText('email', 'email')
57 | ->setAttribute('placeholder', 'email.placeholder')
58 | ->addRule(Form::EMAIL, 'email.invalid')
59 | ->setRequired('email.required');
60 | $form->addSubmit('submit', 'submit')
61 | ->setAttribute('data-loading-text', $this->translator->translate('front.signIn.form.submit.sending'));
62 |
63 | $form->onSuccess[] = [$this, 'processForm'];
64 |
65 | return $form;
66 | }
67 | }
68 |
--------------------------------------------------------------------------------
/app/Modules/Front/Components/Tags/Tags.latte:
--------------------------------------------------------------------------------
1 | {form form, class => ajax}
2 | {* // div.row *}
21 | {/if}
22 |
23 |
24 |
25 |
26 | {ifset $tagsGroups[$tagGroupName]}
27 |
28 | {/ifset}
29 | {="tags.tagGroup." . $tagGroupName|translate}
30 |
38 |
39 | {/foreach}
40 |
{* // div.row *}
41 |
42 | {/form}
43 |
--------------------------------------------------------------------------------
/app/Modules/Api/V1/Model/EventApiService.php:
--------------------------------------------------------------------------------
1 | eventModel = $eventModel;
25 | $this->linkGenerator = $linkGenerator;
26 | }
27 |
28 | /**
29 | * @return Event[]
30 | */
31 | public function getEvents(): array
32 | {
33 | $events = [];
34 |
35 | foreach ($this->eventModel->getAllWithDates([], new DateTime) as $eventRow) {
36 | $event = Event::createFromRow($eventRow);
37 | $eventTags = [];
38 | foreach ($this->eventModel->getEventTags($eventRow) as $eventTag) {
39 | $eventTags[] = [
40 | 'code' => $eventTag->getTag()->getCode(),
41 | 'name' => $eventTag->getTag()->getName(),
42 | 'rate' => $eventTag->getRate(),
43 | ];
44 | }
45 |
46 | $events[] = [
47 | 'id' => $event->getHash(),
48 | 'name' => $event->getName(),
49 | 'description' => $event->getDescription(),
50 | 'url' => $this->linkGenerator->link('Front:Redirect:', [$event->getOriginUrl()]),
51 | 'start' => $event->getStart()->jsonSerialize(),
52 | 'end' => $event->getEnd() ? $event->getEnd()->jsonSerialize() : null,
53 | 'venue' => $event->getVenue(),
54 | 'country' => $event->getCountryCode(),
55 | 'image' => $event->getImage(),
56 | 'tags' => $eventTags,
57 | ];
58 | }
59 |
60 | return $events;
61 | }
62 | }
63 |
--------------------------------------------------------------------------------
/app/Modules/Admin/Model/OrganiserService.php:
--------------------------------------------------------------------------------
1 | eventSeriesModel = $eventSeriesModel;
24 | $this->organiserModel = $organiserModel;
25 | }
26 |
27 | /**
28 | * @return array|\Nette\Database\Table\IRow[]
29 | */
30 | public function getOrganisersSeries(): array
31 | {
32 | return $this->organiserModel->getAll()
33 | ->select('organisers.name AS organiser')
34 | ->select(':events_series.name AS series')
35 | ->select(':events_series.id AS seriesId')
36 | ->fetchAll();
37 | }
38 |
39 | /**
40 | * @param array|\Nette\Database\Table\IRow[] $series
41 | * @return array|\Nette\Database\Table\IRow[]
42 | */
43 | public static function formatSeriesForSelect(array $series): array
44 | {
45 | $result = [];
46 |
47 | foreach ($series as $item) {
48 | $result[$item->seriesId] = $item->organiser
49 | . ($item->series && $item->organiser !== $item->series
50 | ? ': ' . $item->series
51 | : '');
52 | }
53 |
54 | return $result;
55 | }
56 |
57 | public function createOrganiser(string $name, string $url): IRow
58 | {
59 | $organiser = $this->organiserModel->insert([
60 | 'name' => $name,
61 | ]);
62 |
63 | $this->eventSeriesModel->insert([
64 | 'organiser_id' => $organiser->id,
65 | 'name' => $name,
66 | 'url' => $url,
67 | ]);
68 |
69 | return $organiser;
70 | }
71 | }
72 |
--------------------------------------------------------------------------------
/www/flags/flags.min.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * Generated with CSS Flag Sprite generator (https://www.flag-sprites.com/)
3 | */.flag{display:inline-block;width:16px;height:11px;background:url('flags.png') no-repeat}.flag.flag-es{background-position:-80px -11px}.flag.flag-it{background-position:-16px -33px}.flag.flag-al{background-position:0 0}.flag.flag-hu{background-position:-80px -22px}.flag.flag-cz{background-position:-16px -11px}.flag.flag-me{background-position:-16px -44px}.flag.flag-ie{background-position:-96px -22px}.flag.flag-md{background-position:0 -44px}.flag.flag-eu{background-position:-96px -11px}.flag.flag-be{background-position:-48px 0}.flag.flag-hr{background-position:-64px -22px}.flag.flag-lt{background-position:-48px -33px}.flag.flag-si{background-position:-80px -55px}.flag.flag-ro{background-position:-16px -55px}.flag.flag-lu{background-position:-64px -33px}.flag.flag-fi{background-position:0 -22px}.flag.flag-pt{background-position:0 -55px}.flag.flag-ba{background-position:-32px 0}.flag.flag-ru{background-position:-48px -55px}.flag.flag-mk{background-position:-32px -44px}.flag.flag-by{background-position:-96px 0}.flag.flag-de{background-position:-32px -11px}.flag.flag-at{background-position:-16px 0}.flag.flag-mt{background-position:-48px -44px}.flag.flag-nl{background-position:-64px -44px}.flag.flag-se{background-position:-64px -55px}.flag.flag-li{background-position:-32px -33px}.flag.flag-rs{background-position:-32px -55px}.flag.flag-xk{background-position:-32px -66px}.flag.flag-ua{background-position:0 -66px}.flag.flag-no{background-position:-80px -44px}.flag.flag-gb{background-position:-32px -22px}.flag.flag-ch{background-position:0 -11px}.flag.flag-pl{background-position:-96px -44px}.flag.flag-mc{background-position:-96px -33px}.flag.flag-is{background-position:0 -33px}.flag.flag-lv{background-position:-80px -33px}.flag.flag-ee{background-position:-64px -11px}.flag.flag-dk{background-position:-48px -11px}.flag.flag-sk{background-position:-96px -55px}.flag.flag-bg{background-position:-64px 0}.flag.flag-fr{background-position:-16px -22px}.flag.flag-br{background-position:-80px 0}.flag.flag-gr{background-position:-48px -22px}.flag.flag-us{background-position:-16px -66px}
--------------------------------------------------------------------------------
/app/Modules/Core/Model/EventSources/Meetup/MeetupEventSource.php:
--------------------------------------------------------------------------------
1 | apiKey = $apiKey;
29 | }
30 |
31 | /**
32 | * Get upcoming events of the page.
33 | * @return Event[]
34 | */
35 | public function getEvents(string $group): array
36 | {
37 | /** @var MultiResultResponse|MeetupKeyAuthClient $client */
38 | $client = MeetupKeyAuthClient::factory(['key' => $this->apiKey]);
39 | $groupEvents = $client->getGroupEvents(['urlname' => $group])->getData();
40 |
41 | $groupData = $client->getGroup(['urlname' => $group])->getData();
42 | $groupPhoto = isset($groupData['group_photo']) ? $groupData['group_photo']['photo_link'] : null;
43 |
44 | $events = [];
45 | foreach ($groupEvents as $event) {
46 | if ($event['status'] === 'upcoming') {
47 | $events[] = new Event(
48 | null,
49 | $event['name'],
50 | $event['description'] ?? '',
51 | $event['link'],
52 | DateTime::from($event['time'] / 1000),
53 | null,
54 | null,
55 | null,
56 | $groupPhoto ?? null,
57 | Event::calculateRateByAttendeesCount($event['yes_rsvp_count'] + $event['waitlist_count'])
58 | );
59 | }
60 | }
61 |
62 | return $events;
63 | }
64 | }
65 |
--------------------------------------------------------------------------------
/app/Modules/Admin/Model/Authenticator.php:
--------------------------------------------------------------------------------
1 | userModel = $userModel;
22 | }
23 |
24 | /**
25 | * @param string[] $credentials
26 | *
27 | * @throws AuthenticationException
28 | */
29 | public function authenticate(array $credentials): Identity
30 | {
31 | @[$type, $email, $password] = $credentials; // Password is optional, suppress the notice
32 |
33 | // Get user with same email
34 | $user = $this->userModel->getAll()->where([
35 | 'email' => $email,
36 | ])->fetch();
37 |
38 | if ($user !== false) {
39 | switch ($type) {
40 | case UserModel::SUBSCRIPTION_LOGIN:
41 | return new Identity($user->id, null, $user->toArray());
42 | case UserModel::ADMIN_LOGIN:
43 | return $this->logToAdmin($user, $password);
44 | default:
45 | throw new AuthenticationException('Unsupported login type', IAuthenticator::FAILURE);
46 | }
47 | } else {
48 | throw new AuthenticationException(
49 | 'User with this email and password has not been found',
50 | IAuthenticator::IDENTITY_NOT_FOUND
51 | );
52 | }
53 | }
54 |
55 | private function logToAdmin(ActiveRow $user, string $password): Identity
56 | {
57 | if (Passwords::verify($password, $user['password'])) {
58 | return new Identity($user['id'], ['admin'], $user->toArray());
59 | }
60 |
61 | throw new AuthenticationException('Incorrect credentials', IAuthenticator::INVALID_CREDENTIAL);
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/app/Modules/Newsletter/Model/Entity/Newsletter.php:
--------------------------------------------------------------------------------
1 | id;
47 | }
48 |
49 | public function getSubject(): string
50 | {
51 | return $this->subject;
52 | }
53 |
54 | public function getFrom(): string
55 | {
56 | return $this->from;
57 | }
58 |
59 | public function getIntroText(): string
60 | {
61 | return $this->introText;
62 | }
63 |
64 | public function getOutroText(): string
65 | {
66 | return $this->outroText;
67 | }
68 |
69 | public function getAuthor(): string
70 | {
71 | return $this->author;
72 | }
73 |
74 | public function getCreated(): DateTime
75 | {
76 | return $this->created;
77 | }
78 |
79 | public function setSubject(string $subject): void
80 | {
81 | $this->subject = $subject;
82 | }
83 |
84 | public function setFrom(string $from): void
85 | {
86 | $this->from = $from;
87 | }
88 |
89 | public function setIntroText(string $introText): void
90 | {
91 | $this->introText = $introText;
92 | }
93 |
94 | public function setOutroText(string $outroText): void
95 | {
96 | $this->outroText = $outroText;
97 | }
98 |
99 | public function setAuthor(string $author): void
100 | {
101 | $this->author = $author;
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/app/Modules/Core/Presenters/AbstractBasePresenter.php:
--------------------------------------------------------------------------------
1 | translator);
35 | $template->addFilter(null, [Filters::class, 'loader']);
36 |
37 | return $template;
38 | }
39 |
40 | protected function beforeRender(): void
41 | {
42 | parent::beforeRender();
43 |
44 | $this->template->parameters = $this->context->getParameters();
45 | }
46 |
47 | /**
48 | * Log in the user by token (usually provided in url).
49 | *
50 | * @throws BadRequestException
51 | */
52 | protected function loginWithToken(?string $token = null): void
53 | {
54 | if (! $this->getUser()->isLoggedIn()) {
55 | if ($token === null || ($user = $this->userModel->getAll()->where('token', $token)->fetch()) === false
56 | ) {
57 | Debugger::log(sprintf(
58 | 'Invalid user token "%s". Can not login.',
59 | $token
60 | ));
61 |
62 | throw new BadRequestException;
63 | }
64 |
65 | try {
66 | $this->getUser()->login(new Identity($user->id, null, $user->toArray()));
67 | } catch (AuthenticationException $e) {
68 | Debugger::log($e->getMessage(), ILogger::EXCEPTION);
69 | }
70 | }
71 | }
72 | }
73 |
--------------------------------------------------------------------------------
/easy-coding-standard.neon:
--------------------------------------------------------------------------------
1 | includes:
2 | - vendor/symplify/easy-coding-standard/config/symfony-checkers.neon
3 | - vendor/symplify/easy-coding-standard/config/symfony-risky-checkers.neon
4 | - vendor/symplify/easy-coding-standard/config/php54-checkers.neon
5 | - vendor/symplify/easy-coding-standard/config/php70-checkers.neon
6 | - vendor/symplify/easy-coding-standard/config/php71-checkers.neon
7 | - vendor/symplify/easy-coding-standard/config/symplify-checkers.neon
8 | - vendor/symplify/easy-coding-standard/config/common.neon
9 | - vendor/symplify/easy-coding-standard/config/spaces.neon
10 |
11 | checkers:
12 | # Metrics
13 | PHP_CodeSniffer\Standards\Generic\Sniffs\Files\LineLengthSniff:
14 | absoluteLineLimit: 120
15 | PHP_CodeSniffer\Standards\Generic\Sniffs\Metrics\CyclomaticComplexitySniff:
16 | absoluteComplexity: 7
17 | PHP_CodeSniffer\Standards\Generic\Sniffs\Metrics\NestingLevelSniff:
18 | absoluteNestingLevel: 4
19 |
20 | # Type Hints
21 | SlevomatCodingStandard\Sniffs\TypeHints\TypeHintDeclarationSniff:
22 | enableEachParameterAndReturnInspection: true
23 |
24 | # Namespaces
25 | SlevomatCodingStandard\Sniffs\Namespaces\ReferenceUsedNamesOnlySniff:
26 | allowPartialUses: false
27 |
28 | parameters:
29 | exclude_checkers:
30 | - PHP_CodeSniffer\Standards\PSR2\Sniffs\Methods\FunctionCallSignatureSniff
31 | - PhpCsFixer\Fixer\Operator\UnaryOperatorSpacesFixer
32 | - PhpCsFixer\Fixer\Phpdoc\PhpdocAlignFixer
33 | - PhpCsFixer\Fixer\PhpTag\BlankLineAfterOpeningTagFixer
34 | - PhpCsFixer\Fixer\Operator\NewWithBracesFixer
35 | # strict rule on this code
36 | - Symplify\CodingStandard\Fixer\Naming\PropertyNameMatchingTypeFixer
37 | - Symplify\CodingStandard\Sniffs\DependencyInjection\NoClassInstantiationSniff
38 | - Symplify\CodingStandard\Sniffs\Property\DynamicPropertySniff
39 |
40 | skip:
41 | SlevomatCodingStandard\Sniffs\Namespaces\ReferenceUsedNamesOnlySniff:
42 | - app/bootstrap.php
43 |
44 | skip_codes:
45 | - SlevomatCodingStandard\Sniffs\TypeHints\TypeHintDeclarationSniff.UselessDocComment
46 | # many todos
47 | - PHP_CodeSniffer\Standards\Generic\Sniffs\CodeAnalysis\EmptyStatementSniff.DetectedCATCH
48 |
--------------------------------------------------------------------------------
/app/Modules/Front/Components/Settings/Settings.php:
--------------------------------------------------------------------------------
1 | user = $user;
32 | $this->userModel = $userModel;
33 | }
34 |
35 | public function render(): void
36 | {
37 | $this['form']->render();
38 | }
39 |
40 | public function processForm(Form $form): void
41 | {
42 | $values = $form->getValues();
43 |
44 | $this->userModel->getAll()->wherePrimary($this->user->getId())->update([
45 | 'newsletter' => (bool) $values->newsletter,
46 | 'abroad_events' => (bool) $values->abroadEvents,
47 | ]);
48 |
49 | $this->onChange();
50 | }
51 |
52 | protected function createComponentForm(): Form
53 | {
54 | $form = new Form;
55 | $form->setTranslator($this->translator->domain('front.profile.settings.main'));
56 | $form->getElementPrototype()->addClass('ajax');
57 |
58 | // fa-envelope fa-envelope-o
59 | $form->addCheckbox(
60 | 'newsletter',
61 | Html::el('span class="label-icon-end"')
62 | ->addText($this->translator->translate('front.profile.settings.main.newsletter'))
63 | ->addHtml(Html::el('i class="fa fa-envelope"'))
64 | );
65 | // fa-globe
66 | $form->addCheckbox('abroadEvents',
67 | Html::el('span class="label-icon-end"')
68 | ->addText($this->translator->translate('front.profile.settings.main.abroad'))
69 | ->addHtml(Html::el('i class="fa fa-globe"'))
70 | );
71 |
72 | $form->onSuccess[] = [$this, 'processForm'];
73 |
74 | return $form;
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/app/Modules/Front/Components/Subscription/Subscription.php:
--------------------------------------------------------------------------------
1 | userModel = $userModel;
31 | }
32 |
33 | /**
34 | * @throws EmailExistsException
35 | */
36 | public function processForm(Form $form): void
37 | {
38 | $values = $form->getValues();
39 | $user = $this->subscribe($values->email);
40 | $this->onSuccess($user->email);
41 | }
42 |
43 | protected function createComponentForm(): Form
44 | {
45 | $form = new Form;
46 | $form->setTranslator($this->translator->domain('front.subscription.form'));
47 |
48 | $form->addText('email', null, null, 254)
49 | ->setAttribute('placeholder', 'email.placeholder')
50 | ->setAttribute('type', 'email')
51 | ->addCondition(Form::FILLED)
52 | ->addRule(Form::EMAIL, 'email.validation');
53 |
54 | $form->addSubmit('subscribe', 'subscribe.label');
55 |
56 | $form->onSuccess[] = [$this, 'processForm'];
57 |
58 | return $form;
59 | }
60 |
61 | /**
62 | * @throws EmailExistsException
63 | * @throws \PDOException
64 | */
65 | protected function subscribe(string $email): ?IRow
66 | {
67 | try {
68 | $user = $this->userModel->subscribe($email);
69 |
70 | if ($this->getReflection()->getName() === __CLASS__) {
71 | $this->onSuccess($user->email);
72 | } else {
73 | return $user;
74 | }
75 | } catch (EmailExistsException $e) {
76 | $this->onEmailExists($email);
77 | }
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/app/Modules/Admin/Presenters/templates/Dashboard/default.latte:
--------------------------------------------------------------------------------
1 | {layout '../@layout.latte'}
2 |
3 | {block content}
4 |
5 |
6 |
7 |
8 |
9 |
10 | {="admin.dashboard.events.upcoming"|translate: $approvedEventsCounts->all}
11 | {="admin.dashboard.events.thisMonth"|translate}:
12 | {$approvedEventsCounts->thisMonth}
13 |
14 | {="admin.dashboard.events.nextMonth"|translate}:
15 | {$approvedEventsCounts->nextMonth}
16 |
17 |
18 |
19 |
20 |
21 |
26 |
27 |
28 |
29 | {="admin.dashboard.events.notApproved"|translate}
30 | {="admin.dashboard.events.thisMonth"|translate}:
31 | {$notApprovedEventsCounts->thisMonth}
32 |
33 | {="admin.dashboard.events.nextMonth"|translate}:
34 | {$notApprovedEventsCounts->nextMonth}
35 |
36 |
37 |
38 |
39 |
40 | {/block}
--------------------------------------------------------------------------------
/app/Modules/Newsletter/Presenters/NewsletterPresenter.php:
--------------------------------------------------------------------------------
1 | newsletterService = $newsletterService;
31 | $this->userNewsletterModel = $userNewsletterModel;
32 | }
33 |
34 | public function actionDefault(string $hash): void
35 | {
36 | $this->userNewsletter = $this->userNewsletterModel->getAll()->where([
37 | 'hash' => $hash,
38 | ])->fetch();
39 |
40 | if (! $this->userNewsletter) {
41 | $this->error(null, IResponse::S404_NOT_FOUND);
42 | }
43 | }
44 |
45 | public function renderDefault(): void
46 | {
47 | $newsletter = $this->userNewsletter;
48 | $this->template->userNewsletter = NewsletterService::inlineCss($newsletter->toArray());
49 | }
50 |
51 | public function actionUnsubscribe(string $hash): void
52 | {
53 | $userNewsletter = $this->userNewsletterModel->getAll()->where(['hash' => $hash])->fetch();
54 | if ($userNewsletter) {
55 | $this->userModel->getAll()->wherePrimary($userNewsletter->user_id)->update([
56 | 'newsletter' => false,
57 | ]);
58 |
59 | $this->template->email = $userNewsletter->user->email;
60 | }
61 | }
62 |
63 | public function actionDynamic(): void
64 | {
65 | // Allow newsletter preview only for admins
66 | if (! $this->getUser()->isLoggedIn() || ! $this->getUser()->isInRole('admin')) {
67 | $this->redirect(':Admin:Sign:in');
68 | }
69 | }
70 |
71 | public function renderDynamic(int $userId): void
72 | {
73 | $newsletter = $this->newsletterService->buildArrayForTemplate($userId);
74 | $this->template->newsletter = NewsletterService::inlineCss($newsletter);
75 | }
76 | }
77 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Eventigo
2 |
3 | [](https://travis-ci.org/eventigo/eventigo-web)
4 |
5 |
6 | ## Requirements
7 |
8 | - PHP 7.1+
9 | - MySQL 5.6.5+, [5.7 not supported](http://stackoverflow.com/questions/34691059/select-distinct-and-order-by-in-mysql)
10 | - Composer, Bower
11 |
12 | ## First run
13 |
14 | 1. Vytvořit novou MySQL databázi a importovat `events.sql.zip`
15 | 2. Zkopírovat `app/config/templates/config.local.neon` do adresáře `app/config` a upravit konfiguraci
16 | 3. Zkopírovat `phinx.yml.template` jako nový soubor `phinx.yml` a nastavit přístupy do databáze (viz [Phinx docs](http://docs.phinx.org/en/latest/configuration.html))
17 | 4. Nainstalovat závislosti
18 | ```
19 | composer install
20 | bower install
21 | ```
22 | 5. Spustit databázové migrace `vendor/bin/phinx migrate`
23 | 6. Vygenerovat heslo příkazem `php bin/console admin:generatePassword `
24 | 7. Vytvořit admin uživatele v tabulce `users` s vygenerovaným heslem nebo použít demo admin účet: demo@gmail.com, heslo: demo
25 | 8. Přihlásit se na url `/admin`
26 |
27 |
28 | ## Newsletters
29 |
30 | Před vytvořením emailů je možný dynamický preview na adrese `/newsletter/dynamic/`
31 |
32 | 1. Vytvořit záznam v tabulce newsletters - `$ php bin/console newsletters:create`. Použije se poslední podle parametru created. Obsahuje texty, předmět mailu atd.
33 |
34 | 2. Do nového záznamu doplnit `intro_text` a `outro_text` (HTML formát)
35 |
36 | 3. Kontrola možná na adrese `/newsletter/dynamic/`
37 |
38 | 4. Vyrenderování (přípravení) newsletterů pro všechny, kdo má nastavený flag users.newsletter _(true)_
39 | `
40 | $ php bin/console newsletters:render
41 | `
42 |
43 | 5. Preview konkrétního newsletteru na adrese `/newsletter/`
44 | Unsubscribe newsletterů přes link `/newsletter/unsubscribe/`
45 |
46 | 6. Odeslání připravených newsletterů _(nemá nastavené datum odeslání user_newsletter.sent)_
47 | `
48 | $ php bin/console newsletters:send
49 | `
50 |
51 | ## API
52 |
53 | 📚 [Apiary documentation](http://docs.eventigo.apiary.io)
54 |
55 | ## Code style check & fix
56 |
57 | ✅ Check by running:
58 |
59 | ```bash
60 | composer cs
61 | ```
62 |
63 | ✨ Auto-fix by running:
64 |
65 | ```bash
66 | composer fs
67 | ```
68 |
69 | We use [Symplify/EasyCodingStandard](https://github.com/Symplify/EasyCodingStandard) (PHP_CodeSniffer and PHP-CS-Fixer). Thanks to @TomasVotruba!
70 |
71 | ## Exceptions
72 |
73 | Html exceptions lze číst jako admin na url `/admin/exception/[exception-file.html]`
74 |
--------------------------------------------------------------------------------
/app/Modules/Core/Utils/DateTime.php:
--------------------------------------------------------------------------------
1 | translate('front.datetime.' . strtolower(strftime('%A', $a->getTimestamp())));
80 |
81 | if ($b && ($a->format('dmy') !== $b->format('dmy'))) {
82 | // Two day event
83 | $result = $aDayName . $a->format(' j. n. ') . ' – ' . $b->format('j. n. Y');
84 | } else {
85 | // One day event
86 | $result = $aDayName . $a->format(' j. n. Y');
87 | // Add Hour:minute time if its not 00:00
88 | if ((int) $a->format('G') > 0) {
89 | $result .= $a->format(' G:i');
90 | }
91 | }
92 |
93 | return $result;
94 | }
95 | }
96 |
--------------------------------------------------------------------------------
/app/Modules/Front/Presenters/AbstractBasePresenter.php:
--------------------------------------------------------------------------------
1 | updateAccess();
42 | }
43 |
44 | private function updateAccess(): void
45 | {
46 | $access = $this->getSession('access');
47 | $now = new NetteDateTime;
48 |
49 | // Get last access stored in DB
50 | if ($this->getUser()->isLoggedIn()) {
51 | $user = $this->userModel->getAll()
52 | ->wherePrimary($this->getUser()->getId())
53 | ->fetch();
54 | $lastInDb = $user->last_access;
55 | }
56 |
57 | // Store the newest access
58 | $this->lastAccess = DateTime::max($access->last ?? null, $lastInDb ?? null);
59 |
60 | // Update last access
61 | $access->last = clone $now;
62 |
63 | // Update last access in DB if it has been updated few minutes ago or earlier
64 | $syncToDb = $this->context->getParameters()['lastAccess']['syncToDb'] ?? '5 minutes';
65 | if ($this->getUser()->isLoggedIn()
66 | && $this->shouldSyncToDb($access->lastInDb ?? null, $lastInDb ?? null, $syncToDb)
67 | ) {
68 | $this->userModel->getAll()
69 | ->wherePrimary($this->getUser()->getId())
70 | ->update([
71 | 'last_access' => $now,
72 | ]);
73 |
74 | // Update last in DB access
75 | $access->lastInDb = clone $now;
76 | }
77 | }
78 |
79 | private function shouldSyncToDb(
80 | ?NetteDateTime $sessionLastInDb = null,
81 | ?NetteDateTime $lastInDb = null,
82 | string $syncToDb
83 | ): bool {
84 | return ! $sessionLastInDb
85 | || ($lastInDb && $lastInDb > $sessionLastInDb)
86 | || $sessionLastInDb < new NetteDateTime('-' . $syncToDb);
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/app/Modules/Front/Presenters/templates/Homepage/default.latte:
--------------------------------------------------------------------------------
1 | {layout '../@layout.latte'}
2 |
3 | {**
4 | * @param array $events
5 | * @param EventModel $eventModel
6 | * @param int $eventsMaxCount
7 | *}
8 |
9 | {block header}
10 |
66 | {/block}
67 |
68 | {block content}
69 |
70 |
71 |
72 | {control subscriptionTags}
73 |
74 |
75 |
78 | {control eventsList}
79 |
80 |
81 |
82 |
83 | {/block}
84 |
--------------------------------------------------------------------------------
/app/Modules/Front/Presenters/ProfilePresenter.php:
--------------------------------------------------------------------------------
1 | userTagModel = $userTagModel;
35 | $this->settingsFactory = $settingsFactory;
36 | $this->tagsFactory = $tagsFactory;
37 | }
38 |
39 | public function actionSettings(?string $token = null): void
40 | {
41 | // Try to log in the user with provided token
42 | if ($token) {
43 | $this->loginWithToken($token);
44 | }
45 | }
46 |
47 | public function renderSettings(): void
48 | {
49 | $this->template->userData = $this->userModel->getAll()
50 | ->wherePrimary($this->getUser()->getId())->fetch();
51 |
52 | $userTags = $this->userTagModel->getUsersTags($this->user->getId());
53 | $this['tags']['form']->setDefaults(['tags' => $userTags]);
54 |
55 | $user = $this->userModel->getAll()->wherePrimary($this->getUser()->getId())->fetch();
56 | $this['settings-form']->setDefaults([
57 | 'newsletter' => $user->newsletter,
58 | 'abroadEvents' => $user->abroad_events,
59 | ]);
60 | }
61 |
62 | protected function startup(): void
63 | {
64 | parent::startup();
65 |
66 | if (! $this->getUser()->isLoggedIn()) {
67 | $this->flashMessage($this->translator->translate('front.profile.settings.notLoggedIn'));
68 | $this->redirect('Homepage:default');
69 | }
70 | }
71 |
72 | protected function createComponentTags(): Tags
73 | {
74 | $control = $this->tagsFactory->create();
75 |
76 | $control->onChange[] = function (): void {
77 | $this['tags']->redrawControl();
78 | $this->redrawControl('flash-messages');
79 | };
80 |
81 | return $control;
82 | }
83 |
84 | protected function createComponentSettings(): Settings
85 | {
86 | return $this->settingsFactory->create();
87 | }
88 | }
89 |
--------------------------------------------------------------------------------
/www/js/plugins/eventFilter.js:
--------------------------------------------------------------------------------
1 | (function ($, allTags) {
2 |
3 | const showTime = 300;
4 |
5 |
6 | /**
7 | * Plugin initialization
8 | * @returns {jQuery}
9 | */
10 | $.fn.eventFilter = function () {
11 | filter();
12 | this.change(filter);
13 | return this;
14 | };
15 |
16 |
17 | /**
18 | * Filter events by checked tags
19 | */
20 | function filter() {
21 | var tags = getCheckedTags(),
22 | tagsClasses = tags.prefix('.tag-'),
23 | events = $('#events-list');
24 |
25 | if (tagsClasses.length) {
26 | // Hide unchecked tags
27 | events
28 | .find('.event-box:not(' + tagsClasses.join(',') + ')')
29 | .fadeOut(showTime);
30 |
31 | // Show up hidden
32 | var filteredEvents = events.find('.event-box' + tagsClasses.join(','));
33 | filteredEvents.fadeIn(showTime);
34 |
35 | } else {
36 | // Show all up
37 | events
38 | .children()
39 | .fadeIn(showTime);
40 |
41 | filteredEvents = events.children();
42 | tags = allTags;
43 | }
44 |
45 | // Order events by rate
46 | orderByRate(filteredEvents, tags);
47 | }
48 |
49 |
50 | /**
51 | * Return array with checked tags
52 | * @returns {Array}
53 | */
54 | function getCheckedTags() {
55 | var checked = $('#tags').find('input:checked');
56 |
57 | var tags = [];
58 | checked.each(function (index, el) {
59 | tags.push($(el).parent().data('tag'));
60 | });
61 |
62 | return tags;
63 | }
64 |
65 |
66 | /**
67 | * Order events by calculated rate
68 | * @param events
69 | * @param tags
70 | */
71 | function orderByRate(events, tags) {
72 | var rates = calculateRates(events, tags);
73 | events.sort(function (a, b) {
74 | return rates[a.id] > rates[b.id] ? -1 : 1;
75 | }).appendTo('#events-list');
76 | }
77 |
78 |
79 | /**
80 | * Order events by calculated rate
81 | * @param events
82 | * @param tags
83 | */
84 | function calculateRates(events, tags) {
85 | var eventsRates = [];
86 |
87 | events.each(function (i, el) {
88 | eventsRates[el.id] = 0;
89 |
90 | var rates = $(el).data('rates');
91 |
92 | // Event rate
93 | var eventRate = rates.event;
94 |
95 | // Tag rates
96 | tags.forEach(function (tag) {
97 | var tagRate = rates[tag] || 0;
98 | eventsRates[el.id] += eventRate * tagRate;
99 | });
100 | });
101 |
102 | return eventsRates;
103 | }
104 |
105 | }(jQuery, allTags));
--------------------------------------------------------------------------------
/app/Modules/Email/Model/EmailService.php:
--------------------------------------------------------------------------------
1 | sendGrid = $sendGrid;
48 | $this->templateFactory = $templateFactory;
49 | $this->linkGenerator = $linkGenerator;
50 | $this->translator = $translator;
51 | }
52 |
53 | public function sendLogin(string $emailTo, string $token): void
54 | {
55 | $to = new Email(null, $emailTo);
56 | $from = new Email('Eventigo.cz', 'prihlaseni@eventigo.cz');
57 | $subject = $this->translator->translate('email.login.subject');
58 | $content = new Content('text/html', $this->renderLoginEmail($token));
59 |
60 | $mail = new Mail($from, $subject, $to, $content);
61 | $mail->addCategory('emailLogin');
62 |
63 | try {
64 | $this->sendGrid->client->mail()->send()->post($mail);
65 | } catch (Throwable $throwable) {
66 | // TODO log unsuccessful email send
67 | }
68 | }
69 |
70 | public function renderLoginEmail(string $token): string
71 | {
72 | $email = new BasicEmail;
73 | $email->setIntroText($this->translator->translate('email.login.text'));
74 | $email->setButtonText($this->translator->translate('email.login.loginButton'));
75 | $email->setButtonUrl(new Url($this->linkGenerator->link('Front:Homepage:default', ['token' => $token])));
76 | $email->setFooterText($this->translator->translate('email.login.footerText'));
77 |
78 | /** @var Template $template */
79 | $template = $this->templateFactory->createTemplate();
80 | Filters::setTranslator($this->translator);
81 | $template->addFilter(null, [Filters::class, 'loader']);
82 |
83 | return $template->getLatte()->renderToString(EmailPresenter::BASIC_EMAIL_TEMPLATE_FILE, [
84 | 'email' => $email,
85 | ]);
86 | }
87 | }
88 |
--------------------------------------------------------------------------------
/app/Modules/Admin/Presenters/SourcesPresenter.php:
--------------------------------------------------------------------------------
1 | sourceService = $sourceService;
44 | $this->sourceModel = $sourceModel;
45 | $this->sourcesTableFactory = $sourcesTableFactory;
46 | $this->sourceFormFactory = $sourceFormFactory;
47 | }
48 |
49 | public function actionCreate(): void
50 | {
51 | $defaults = [
52 | 'frequencyNumber' => 1,
53 | 'nextCheck' => (new NetteDateTime('+1 days'))->format(DateTime::DATE_FORMAT),
54 | 'createOrganiser' => true,
55 | ];
56 | $this['sourceForm-form']->setDefaults($defaults);
57 | }
58 |
59 | protected function createComponentSourceForm(): SourceForm
60 | {
61 | $control = $this->sourceFormFactory->create();
62 |
63 | $control->onCreate[] = function (ActiveRow $source): void {
64 | $this->flashMessage($this->translator->translate('admin.sourceForm.success'), 'success');
65 |
66 | // Crawl recently added source
67 | $addedEvents = $this->sourceService->crawlSource($source);
68 | if ($addedEvents > 0) {
69 | $this->flashMessage($this->translator->translate('admin.events.crawlSources.success',
70 | $addedEvents, ['events' => $addedEvents]), 'success');
71 | } else {
72 | $this->flashMessage($this->translator->translate('admin.events.crawlSources.noEvents'));
73 | }
74 |
75 | $this->redirect('Sources:default');
76 | };
77 |
78 | $control->onUpdate[] = function (ActiveRow $source): void {
79 | $this->flashMessage($this->translator->translate('admin.sourceForm.success'), 'success');
80 | $this->redirect('Sources:update', ['id' => $source->id]);
81 | };
82 |
83 | return $control;
84 | }
85 |
86 | protected function createComponentSourcesTable(): SourcesTable
87 | {
88 | return $this->sourcesTableFactory->create($this->sourceModel->getAll()->select('id, name, url'));
89 | }
90 | }
91 |
--------------------------------------------------------------------------------
/app/lang/front.cs_CZ.neon:
--------------------------------------------------------------------------------
1 | homepage.fbLogin.failed: Přihlášení přes facebook neproběhlo v pořádku
2 | homepage.header.logo: eventigo
3 | homepage.header.title: Každý týden výběr akcí přesně pro tebe
4 | homepage.header.mainTitle: "Eventy, které tě zajímají"
5 | homepage.header.howItWorks: "Každý čtvrtek ti pošleme přehled zajímavých akcí na následující týden. Události za tebe hledáme napříč několika zdroji."
6 | homepage.header.subTitle: "Email každý týden s výběrem akcí přesně pro tebe"
7 | homepage.header.button: To chci vyzkoušet
8 | homepage.tags.header: "1 Jaké akce chceš dostávat?"
9 | homepage.events.header: Ukázka akcí z newsletteru
10 | subscription.form.email.placeholder: tvuj@email.cz
11 | subscription.form.email.validation: Email je špatně. Zadej správně svou emailovou adresu
12 | subscription.form.email.required: Zadej svou emailovou adresu
13 | subscription.form.subscribe.label: Chci dostávat týdenní výběr akcí
14 | subscription.form.email.label: "2 Kam chceš akce posílat?"
15 | subscription.message.emailExists: "Email %email% je již zaregistrován. Poslali jsme ti odkaz pro přihlášení"
16 | subscription.message.success: "Jsme rádi, že tě Eventigo zaujalo. Na adresu %email% ti bude pravidelně chodit přehled akcí."
17 | events.thisWeek.title: Tento týden
18 | events.thisMonth.title: Tento měsíc
19 | events.nextMonth.title: Příští měsíc
20 | events.upcoming.title: Další akce
21 | eventList.label.new: nové
22 | eventList.label.today: dnes
23 | eventList.label.recentlyAdded: nedávno přidané
24 | eventList.noEvents.title: Pro tvoje sledované tagy nejsou aktuálně žádné akce
25 | eventList.noEvents.settingsInfo: "Mrkni do nastavení tagů, zda tě nezaujme nějaké další téma, které sledovat"
26 | eventList.noEvents.linkToSettings: Nastavení sledovaných tagů
27 | eventList.noEvents.discoverInfo: Projdi si ostatní nadcházející akce
28 | eventList.noEvents.linkToDiscover: Procházet akce
29 | newsletter.header.title: Výběr nadcházejících akcí
30 | datetime.monday: Pondělí
31 | datetime.tuesday: Úterý
32 | datetime.wednesday: Středa
33 | datetime.thursday: Čtvrtek
34 | datetime.friday: Pátek
35 | datetime.saturday: Sobota
36 | datetime.sunday: Neděle
37 | footer.text: "© 2016 eventigo Made with ♥ in Prague"
38 | meta.description: "Každý týden výběr akcí přesně pro tebe. Konference, meetupy, přednášky, workshopy a další."
39 | meta.title: "Eventy na tento týden"
40 | nav.myFeed: Můj feed
41 | nav.discover: Procházet akce
42 | nav.settings: Nastavení
43 | nav.signOut: Odhlásit se
44 | nav.login: Přihlásit se
45 | nav.login.facebook: Přihlásit se přes Facebook
46 | nav.login.email: Přihlásit se přes email
47 | nav.user: Uživatel
48 | profile.settings.main.newsletter: Chci dostávat email s výběrem akcí
49 | profile.settings.main.abroad: Zobrazovat akce v zahraničí
50 | profile.settings.main.title: Hlavní nastavení
51 | profile.settings.tags.title: Sledování tagů
52 | profile.settings.afterLogin: "Nastav si sledování tagů, které tě zajímají"
53 | profile.settings.notLoggedIn: Pro nastavení tagů se prosím přihlaš tlačítkem vpravo nahoře.
54 | sign.out: Byl jsi odhlášen. %bye%
55 | signIn.form.email: email
56 | signIn.form.email.placeholder: tvuj@email.cz
57 | signIn.form.email.invalid: "Zadej platný email, ať ti tam pošleme odkaz pro přihlášení"
58 | signIn.form.email.required: "Zadej email, ať ti tam pošleme odkaz pro přihlášení"
59 | signIn.form.submit: Získat přihlašovací odkaz
60 | signIn.form.submit.sending: Zasílám email
61 | signIn.form.success: Na email %email% jsme ti poslali odkaz pro přihlášení
62 | signIn.form.title: Přihlášení přes email
63 |
--------------------------------------------------------------------------------
/app/Modules/Admin/Components/SourceForm/SourceForm.php:
--------------------------------------------------------------------------------
1 | organiserService = $organiserService;
38 | $this->sourceModel = $sourceModel;
39 | }
40 |
41 | public function render(): void
42 | {
43 | $this['form']->render();
44 | }
45 |
46 | public function processForm(Form $form): void
47 | {
48 | $values = $form->getValues();
49 |
50 | if (! $values->id) {
51 | if ($values->createOrganiser) {
52 | $organiser = $this->organiserService->createOrganiser($values->name, $values->url);
53 | }
54 |
55 | $source = $this->sourceModel->insert([
56 | 'name' => $values->name ?: null,
57 | 'url' => $values->url ?: null,
58 | 'check_frequency' => $checkFrequency = SourceModel::FREQUENCY_TYPES[$values->frequency],
59 | 'next_check' => $values->nextCheck
60 | ? NetteDateTime::createFromFormat(DateTime::DATE_FORMAT, $values->nextCheck)
61 | : new NetteDateTime('+' . $checkFrequency . ' days'),
62 | 'event_series_id' => isset($organiser)
63 | ? $organiser->related('events_series')->fetch()->id
64 | : null,
65 | ]);
66 |
67 | $this->onCreate($source);
68 | } else {
69 | $source = $this->sourceModel->update($values);
70 | $this->onUpdate($source);
71 | }
72 | }
73 |
74 | protected function createComponentForm(): Form
75 | {
76 | $form = new Form;
77 | $form->setTranslator($this->translator->domain('admin.sourceForm'));
78 |
79 | // Event
80 | $form->addGroup();
81 | $form->addText('name', 'name')
82 | ->setAttribute('autofocus', true);
83 | $form->addText('url', 'url')
84 | ->addCondition(Form::FILLED)
85 | ->addRule(Form::URL, 'url.wrong');
86 | $form->addSelect('frequency', 'frequency')
87 | ->setItems(array_combine(
88 | $frequencyTypes = array_keys(SourceModel::FREQUENCY_TYPES),
89 | Collection::prefix($frequencyTypes, 'frequency.')
90 | ));
91 | $form->addText('nextCheck', 'nextCheck')
92 | ->setAttribute('class', 'date');
93 |
94 | $form->addCheckbox('createOrganiser', 'createOrganiser');
95 |
96 | $form->addHidden('id');
97 |
98 | $form->addSubmit('save', 'save')
99 | ->setAttribute('class', 'btn btn-success');
100 | $form->onSubmit[] = [$this, 'processForm'];
101 |
102 | return $form;
103 | }
104 | }
105 |
--------------------------------------------------------------------------------
/app/Modules/Admin/Model/SourceService.php:
--------------------------------------------------------------------------------
1 | eventModel = $eventModel;
48 | $this->meetupSource = $meetupSource;
49 | $this->srazySource = $srazySource;
50 | $this->fbSource = $fbSource;
51 | $this->sourceModel = $sourceModel;
52 | }
53 |
54 | public function crawlSources(): int
55 | {
56 | $addedEvents = 0;
57 |
58 | // TODO Check sources by frequency
59 | $sources = $this->sourceModel->getAll();
60 | foreach ($sources as $source) {
61 | $addedEvents += $this->crawlSource($source);
62 | }
63 |
64 | return $addedEvents;
65 | }
66 |
67 | public function crawlSource(IRow $source): int
68 | {
69 | $addedEvents = 0;
70 |
71 | $events = [];
72 | $sourceUrl = new Url($source['url']);
73 | if (FacebookEventSource::isSource($source['url'])) {
74 | $pageId = trim($sourceUrl->getPath(), '/');
75 | $events = $this->fbSource->getEvents($pageId);
76 | } elseif (SrazyEventSource::isSource($source['url'])) {
77 | $pathParts = array_filter(explode('/', $sourceUrl->getPath()));
78 | $series = reset($pathParts);
79 | $events = $this->srazySource->getEvents($series);
80 | } elseif (MeetupEventSource::isSource($source['url'])) {
81 | $pathParts = array_filter(explode('/', $sourceUrl->getPath()));
82 | $group = reset($pathParts);
83 | $events = $this->meetupSource->getEvents($group);
84 | }
85 |
86 | foreach ($events as $event) {
87 | $existingEvent = $this->eventModel->findExistingEvent($event);
88 |
89 | if (! $existingEvent) {
90 | try {
91 | $this->eventModel->insert([
92 | 'name' => $event->getName(),
93 | 'description' => $event->getDescription(),
94 | 'start' => $event->getStart(),
95 | 'end' => $event->getEnd(),
96 | 'origin_url' => $event->getOriginUrl(),
97 | 'image' => $event->getImage(),
98 | 'rate' => $event->getRate(),
99 | 'event_series_id' => $source['event_series_id'],
100 | ]);
101 | } catch (UniqueConstraintViolationException $e) {
102 | continue;
103 | }
104 |
105 | ++$addedEvents;
106 | }
107 | }
108 |
109 | return $addedEvents;
110 | }
111 | }
112 |
--------------------------------------------------------------------------------
/app/Modules/Core/Model/EventSources/Facebook/FacebookEventSource.php:
--------------------------------------------------------------------------------
1 | facebook = $facebook;
35 | }
36 |
37 | public function getEventById(string $id): Event
38 | {
39 | try {
40 | $response = $this->facebook->api(
41 | '/' . $id,
42 | 'GET',
43 | ['fields' => self::EVENT_FIELDS]
44 | );
45 |
46 | return new Event(
47 | null,
48 | $response->name,
49 | $response->description ?? '',
50 | 'https://facebook.com/events/' . $id . '/',
51 | isset($response->start_time) ? DateTime::createFromFormat(DATE_ISO8601, $response->start_time) : null,
52 | isset($response->end_time) ? DateTime::createFromFormat(DATE_ISO8601, $response->end_time) : null,
53 | $response->place->location->city ?? null,
54 | null,
55 | $response->cover->source ?? null,
56 | Event::calculateRateByAttendeesCount($response->interested_count + $response->attending_count)
57 | );
58 | } catch (FacebookApiException $e) {
59 | Debugger::log($e, Debugger::EXCEPTION);
60 |
61 | throw $e;
62 | }
63 | }
64 |
65 | /**
66 | * Get upcoming events of the page.
67 | * @return Event[]
68 | */
69 | public function getEvents(string $pageId): array
70 | {
71 | $response = $this->facebook->api(
72 | '/' . $pageId, 'GET', [
73 | 'fields' => 'events{' . self::EVENT_FIELDS . '}',
74 | ]
75 | );
76 |
77 | $events = [];
78 | if (isset($response->events)) {
79 | $fbEvents = $response->events->data;
80 | foreach ($fbEvents as $event) {
81 | $start = isset($event->start_time) ? $this->createIsoDateTime($event->start_time) : null;
82 | if ($start && $start > new DateTime) {
83 | $events[] = new Event(
84 | null,
85 | $event->name,
86 | $event->description ?? '',
87 | 'https://facebook.com/events/' . $event->id . '/',
88 | $start,
89 | isset($event->end_time) ? DateTime::createFromFormat(DATE_ISO8601, $event->end_time) : null,
90 | $event->place->location->city ?? null,
91 | null,
92 | $event->cover->source ?? null,
93 | Event::calculateRateByAttendeesCount($event->interested_count + $event->attending_count)
94 | );
95 | }
96 | }
97 | }
98 |
99 | return $events;
100 | }
101 |
102 | private function createIsoDateTime(string $time): DateTime
103 | {
104 | return DateTime::createFromFormat(DATE_ISO8601, $time);
105 | }
106 | }
107 |
--------------------------------------------------------------------------------
/app/Modules/Front/Components/Tags/Tags.php:
--------------------------------------------------------------------------------
1 | tagModel = $tagModel;
56 | $this->userTagModel = $userTagModel;
57 | $this->tagGroupModel = $tagGroupModel;
58 | $this->tags = $this->tagModel->getAllByMostEvents();
59 | $this->user = $user;
60 | }
61 |
62 | public function render(): void
63 | {
64 | $this->template->tags = $this->tags->fetchPairs('code');
65 | $this->template->tagsGroups = $this->tagGroupModel->getAll()
66 | ->where('icon IS NOT NULL')
67 | ->fetchPairs('name', 'icon');
68 | $this->template->columnsPerRow = 4;
69 | parent::render();
70 | }
71 |
72 | public function processForm(Form $form): void
73 | {
74 | $values = $form->getValues();
75 |
76 | // Store user's selected tags
77 | $chosenTags = Collection::getNestedValues((array) $values->tags);
78 | $tags = $this->tagModel->getAll()->where('code IN (?)', $chosenTags)->fetchAll();
79 |
80 | foreach ($tags as $tag) {
81 | try {
82 | // Add newly checked tags
83 | $this->userTagModel->insert([
84 | 'tag_id' => $tag->id,
85 | 'user_id' => $this->user->getId(),
86 | ]);
87 | } catch (UniqueConstraintViolationException $e) {
88 | // Tag has been already inserted
89 | }
90 | }
91 |
92 | // Remove not checked tags
93 | if ($chosenTags) {
94 | $this->userTagModel->delete([
95 | 'user_id' => $this->user->getId(),
96 | 'tag_id NOT IN (?)' => $this->tagModel->getAll()
97 | ->where('code IN (?)', $chosenTags)
98 | ->fetchPairs(null, 'id'),
99 | ]);
100 | } else {
101 | $this->userTagModel->delete([
102 | 'user_id' => $this->user->getId(),
103 | ]);
104 | }
105 |
106 | $this->onChange();
107 | }
108 |
109 | protected function createComponentForm(): Form
110 | {
111 | $form = new Form;
112 |
113 | $tagsGroups = $this->tags->fetchAssoc('tagGroupName|id');
114 | $tagsContainer = $form->addContainer('tags');
115 | foreach ($tagsGroups as $tagGroupName => $tagsGroup) {
116 | $tagsContainer->addCheckboxList($tagGroupName)
117 | ->setItems(Helpers::toPairs($tagsGroups[$tagGroupName], 'code', 'name'))
118 | ->setTranslator(null);
119 | }
120 |
121 | $form->onSuccess[] = [$this, 'processForm'];
122 |
123 | return $form;
124 | }
125 | }
126 |
--------------------------------------------------------------------------------
/app/Modules/Newsletter/Presenters/templates/@layout.latte:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | {ifset title}{include title|striptags} | {/ifset}Eventigo.cz
7 |
8 | {* favicon *}
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 | {* Open Graph Markup *}
33 |
34 |
35 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
43 | {* Styles *}
44 |
45 |
46 |
47 | {block head}{/block}
48 |
49 |
50 |
51 | {include content}
52 |
53 | {block scripts}{/block}
54 |
55 | {* Google Analytics if key is set in local config *}
56 | {* TODO: put in separate file *}
57 | {if !empty($parameters['googleAnalyticsKey'])}
58 |
67 | {/if}
68 |
69 |
70 |
80 |
81 |
82 |
--------------------------------------------------------------------------------