├── .editorconfig ├── .env.example ├── .github ├── CODEOWNERS ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── WORKFLOW.md └── workflows │ └── test.yml ├── .gitignore ├── .gitignore.dist ├── .prettierrc ├── .styleci.yml ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── composer.json ├── config ├── analytics.php ├── blade-icons.php ├── cms.php ├── cms │ ├── editor.php │ ├── embeds.php │ ├── enabled.php │ ├── form.php │ ├── menu.php │ └── social.php ├── deploy.php ├── htmlmin.php ├── seotools.php ├── translatable.php ├── twill-navigation.php ├── twill.php └── twill │ ├── block_editor.php │ ├── dashboard.php │ ├── file_library.php │ ├── glide.php │ └── media_library.php ├── database ├── factories │ ├── BlockFactory.php │ ├── CategoryFactory.php │ ├── CityLabFactory.php │ ├── FormFactory.php │ ├── MediaFactory.php │ ├── MenuFactory.php │ ├── PageFactory.php │ ├── PartnerFactory.php │ ├── PersonFactory.php │ ├── ResponseFactory.php │ └── UserFactory.php └── migrations │ ├── default │ ├── 2020_02_10_000001_create_pages_tables.php │ ├── 2020_02_10_000002_create_menus_tables.php │ ├── 2020_02_10_000003_create_forms_tables.php │ ├── 2020_02_10_000004_create_responses_tables.php │ ├── 2020_02_10_000005_create_partners_tables.php │ ├── 2020_02_10_000006_create_posts_tables.php │ └── 2020_02_10_000007_create_categories_tables.php │ └── people │ ├── 2020_05_04_000001_create_city_labs_tables.php │ └── 2020_05_04_000002_create_people_tables.php ├── deploy └── assets.php ├── package.json ├── phpcs.xml ├── phpstan.neon ├── phpunit.xml ├── resources ├── assets │ ├── ckeditor │ │ ├── src │ │ │ └── ckeditor.js │ │ └── webpack.config.js │ ├── css │ │ ├── app.pcss │ │ ├── content.pcss │ │ └── fonts.pcss │ ├── fonts │ │ └── titillium-web-latin-ext │ │ │ ├── 200-italic.woff │ │ │ ├── 200-italic.woff2 │ │ │ ├── 200.woff │ │ │ ├── 200.woff2 │ │ │ ├── 300-italic.woff │ │ │ ├── 300-italic.woff2 │ │ │ ├── 300.woff │ │ │ ├── 300.woff2 │ │ │ ├── 400-italic.woff │ │ │ ├── 400-italic.woff2 │ │ │ ├── 400.woff │ │ │ ├── 400.woff2 │ │ │ ├── 600-italic.woff │ │ │ ├── 600-italic.woff2 │ │ │ ├── 600.woff │ │ │ ├── 600.woff2 │ │ │ ├── 700-italic.woff │ │ │ ├── 700-italic.woff2 │ │ │ ├── 700.woff │ │ │ ├── 700.woff2 │ │ │ ├── 900.woff │ │ │ └── 900.woff2 │ ├── js │ │ ├── app.js │ │ └── components │ │ │ ├── .gitignore │ │ │ └── Ckeditor.vue │ └── svg │ │ ├── 404.svg │ │ ├── hex.svg │ │ ├── icons │ │ ├── author.svg │ │ ├── calendar.svg │ │ ├── camera-lens.svg │ │ ├── camera.svg │ │ ├── category.svg │ │ ├── community.svg │ │ ├── dropdown.svg │ │ ├── facebook.svg │ │ ├── github.svg │ │ ├── global.svg │ │ ├── instagram.svg │ │ ├── linkedin.svg │ │ ├── quote.svg │ │ ├── tiktok.svg │ │ ├── triangle-down.svg │ │ ├── triangle-up.svg │ │ └── twitter.svg │ │ ├── logo-gray.svg │ │ ├── logo-sm-gray.svg │ │ ├── logo-sm.svg │ │ └── logo.svg ├── lang │ ├── en │ │ ├── admin.php │ │ ├── date.php │ │ ├── email.php │ │ ├── errors.php │ │ ├── footer.php │ │ ├── form.php │ │ ├── misc.php │ │ ├── pagination.php │ │ └── validation.php │ ├── hu │ │ └── errors.php │ └── ro │ │ ├── date.php │ │ └── errors.php └── views │ ├── admin │ ├── blocks │ │ ├── accordion.blade.php │ │ ├── button.blade.php │ │ ├── callToAction.blade.php │ │ ├── cityLabs.blade.php │ │ ├── counter.blade.php │ │ ├── embed.blade.php │ │ ├── form.blade.php │ │ ├── formSection.blade.php │ │ ├── hero.blade.php │ │ ├── html.blade.php │ │ ├── imageGrid.blade.php │ │ ├── imageText.blade.php │ │ ├── menuItem.blade.php │ │ ├── newsletter.blade.php │ │ ├── notice.blade.php │ │ ├── partners.blade.php │ │ ├── people.blade.php │ │ ├── quote.blade.php │ │ ├── separator.blade.php │ │ └── text.blade.php │ ├── categories │ │ └── form.blade.php │ ├── cityLabs │ │ └── form.blade.php │ ├── forms │ │ └── form.blade.php │ ├── menus │ │ └── form.blade.php │ ├── pages │ │ └── form.blade.php │ ├── partners │ │ └── form.blade.php │ ├── people │ │ └── form.blade.php │ ├── posts │ │ └── form.blade.php │ ├── repeaters │ │ ├── accordionItem.blade.php │ │ ├── counterItem.blade.php │ │ ├── formField.blade.php │ │ └── subMenuItem.blade.php │ ├── responses │ │ └── form.blade.php │ ├── settings │ │ ├── mailchimp.blade.php │ │ ├── site.blade.php │ │ └── social.blade.php │ └── utils │ │ ├── ckeditor.blade.php │ │ └── menu-item-template.blade.php │ ├── components │ ├── button.blade.php │ ├── container.blade.php │ ├── figure.blade.php │ ├── hex-badge.blade.php │ ├── image.blade.php │ ├── input.blade.php │ ├── link.blade.php │ ├── notice.blade.php │ └── social-network-link.blade.php │ ├── emails │ └── formResponseSubmitted.blade.php │ ├── errors │ └── 404.blade.php │ └── front │ ├── blocks │ ├── _container.blade.php │ ├── accordion.blade.php │ ├── accordionItem.blade.php │ ├── button.blade.php │ ├── callToAction.blade.php │ ├── cityLabs.blade.php │ ├── counter.blade.php │ ├── counterItem.blade.php │ ├── embed.blade.php │ ├── form.blade.php │ ├── formSection.blade.php │ ├── hero.blade.php │ ├── html.blade.php │ ├── imageGrid.blade.php │ ├── imageText.blade.php │ ├── newsletter.blade.php │ ├── notice.blade.php │ ├── partners.blade.php │ ├── people.blade.php │ ├── quote.blade.php │ ├── separator.blade.php │ └── text.blade.php │ ├── categories │ └── show.blade.php │ ├── cityLabs │ ├── index.blade.php │ └── show.blade.php │ ├── form │ ├── _error.blade.php │ ├── _field.blade.php │ ├── checkbox.blade.php │ ├── input.blade.php │ ├── radio.blade.php │ ├── select.blade.php │ └── textarea.blade.php │ ├── layouts │ ├── app.blade.php │ └── blocks.blade.php │ ├── menu │ ├── footer.blade.php │ └── header.blade.php │ ├── pages │ └── show.blade.php │ ├── partials │ ├── analytics.blade.php │ ├── footer.blade.php │ ├── header.blade.php │ ├── notice.blade.php │ ├── page-header.blade.php │ ├── pagination.blade.php │ └── scripts.blade.php │ ├── partners │ └── card.blade.php │ ├── people │ └── card.blade.php │ └── posts │ ├── card.blade.php │ ├── index.blade.php │ └── show.blade.php ├── routes ├── admin.php └── web.php ├── src ├── CmsServiceProvider.php ├── Commands │ ├── Build.php │ ├── Command.php │ └── Install.php ├── Faker │ └── PicsumImageProvider.php ├── FakerServiceProvider.php ├── Helpers │ ├── MailchimpHelper.php │ ├── SettingsHelper.php │ ├── SocialHelper.php │ └── UrlHelper.php ├── Http │ ├── Controllers │ │ ├── Admin │ │ │ ├── CategoryController.php │ │ │ ├── CityLabController.php │ │ │ ├── FormController.php │ │ │ ├── MenuController.php │ │ │ ├── ModuleController.php │ │ │ ├── PageController.php │ │ │ ├── PartnerController.php │ │ │ ├── PersonController.php │ │ │ ├── PostController.php │ │ │ └── ResponseController.php │ │ └── Front │ │ │ ├── CategoryController.php │ │ │ ├── CityLabController.php │ │ │ ├── Controller.php │ │ │ ├── FormController.php │ │ │ ├── NewsletterSubscriptionController.php │ │ │ ├── PageController.php │ │ │ └── PostController.php │ ├── Middleware │ │ ├── Authenticate.php │ │ ├── DefaultSeoConfig.php │ │ └── RedirectTrailingSlash.php │ └── Requests │ │ ├── Admin │ │ ├── CategoryRequest.php │ │ ├── CityLabRequest.php │ │ ├── FormRequest.php │ │ ├── MenuRequest.php │ │ ├── PageRequest.php │ │ ├── PartnerRequest.php │ │ ├── PersonRequest.php │ │ └── PostRequest.php │ │ └── Front │ │ ├── FormRequest.php │ │ └── NewsletterSubscriptionRequest.php ├── LocaleServiceProvider.php ├── Mail │ └── FormResponseSubmitted.php ├── Models │ ├── BaseModel.php │ ├── Category.php │ ├── CityLab.php │ ├── Form.php │ ├── Menu.php │ ├── Page.php │ ├── Partner.php │ ├── Person.php │ ├── Post.php │ ├── Response.php │ ├── Revisions │ │ ├── CityLabRevision.php │ │ ├── PageRevision.php │ │ └── PostRevision.php │ ├── Slugs │ │ ├── CategorySlug.php │ │ ├── CityLabSlug.php │ │ ├── PageSlug.php │ │ └── PostSlug.php │ └── Translations │ │ ├── CategoryTranslation.php │ │ ├── CityLabTranslation.php │ │ ├── FormTranslation.php │ │ ├── MenuTranslation.php │ │ ├── PageTranslation.php │ │ ├── PersonTranslation.php │ │ └── PostTranslation.php ├── ObserverServiceProvider.php ├── Observers │ ├── MenuObserver.php │ └── SettingTranslationObserver.php ├── Presenters │ ├── BlockPresenter.php │ ├── PartnerPresenter.php │ ├── PersonPresenter.php │ ├── PostPresenter.php │ ├── Presenter.php │ └── ResponsePresenter.php ├── Repositories │ ├── CategoryRepository.php │ ├── CityLabRepository.php │ ├── FormRepository.php │ ├── MenuRepository.php │ ├── PageRepository.php │ ├── PartnerRepository.php │ ├── PersonRepository.php │ ├── PostRepository.php │ └── ResponseRepository.php ├── RouteServiceProvider.php ├── Rules │ └── OneEmailPerLine.php └── Traits │ └── WithSeoTags.php ├── tailwind.config.js ├── tests ├── TestCase.php └── Unit │ ├── Helpers │ ├── SettingsHelperTest.php │ ├── SocialHelperTest.php │ └── UrlHelperTest.php │ ├── Http │ ├── Controllers │ │ └── Front │ │ │ ├── CityLabControllerTest.php │ │ │ └── PageControllerTest.php │ └── Middleware │ │ ├── DefaultSeoConfigTest.php │ │ └── RedirectTrailingSlashTest.php │ ├── Models │ ├── CityLabTest.php │ ├── FormTest.php │ ├── MenuTest.php │ ├── PartnerTest.php │ └── PersonTest.php │ ├── Presenters │ ├── BlockPresenterTest.php │ ├── PartnerPresenterTest.php │ └── PersonPresenterTest.php │ ├── Rules │ └── OneEmailPerLineTest.php │ └── Traits │ └── WithSeoTagsTest.php └── webpack.mix.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 4 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | end_of_line = lf 10 | 11 | [*.{yml,yaml}] 12 | indent_size = 2 13 | -------------------------------------------------------------------------------- /.env.example: -------------------------------------------------------------------------------- 1 | APP_NAME= 2 | APP_ENV=local 3 | APP_KEY= 4 | APP_DEBUG=true 5 | APP_URL=localhost 6 | 7 | ADMIN_APP_URL=localhost 8 | ADMIN_APP_PATH=admin 9 | 10 | LOG_CHANNEL=stack 11 | 12 | DB_CONNECTION=mysql 13 | DB_HOST=127.0.0.1 14 | DB_PORT=3306 15 | DB_DATABASE=laravel 16 | DB_USERNAME=root 17 | DB_PASSWORD= 18 | 19 | BROADCAST_DRIVER=log 20 | CACHE_DRIVER=file 21 | QUEUE_CONNECTION=sync 22 | SESSION_DRIVER=file 23 | SESSION_LIFETIME=120 24 | 25 | REDIS_HOST=127.0.0.1 26 | REDIS_PASSWORD=null 27 | REDIS_PORT=6379 28 | 29 | MAIL_DRIVER=smtp 30 | MAIL_HOST=smtp.mailtrap.io 31 | MAIL_PORT=2525 32 | MAIL_USERNAME=null 33 | MAIL_PASSWORD=null 34 | MAIL_ENCRYPTION=null 35 | MAIL_FROM_ADDRESS=null 36 | MAIL_FROM_NAME="${APP_NAME}" 37 | 38 | AWS_ACCESS_KEY_ID= 39 | AWS_SECRET_ACCESS_KEY= 40 | AWS_DEFAULT_REGION= 41 | AWS_BUCKET= 42 | AWS_USE_HTTPS=true 43 | 44 | MEDIA_LIBRARY_ENDPOINT_TYPE= 45 | MEDIA_LIBRARY_CASCADE_DELETE=true 46 | MEDIA_LIBRARY_ACL=private 47 | MEDIA_LIBRARY_FILESIZE_LIMIT=50 48 | 49 | FILE_LIBRARY_ENDPOINT_TYPE= 50 | FILE_LIBRARY_CASCADE_DELETE=true 51 | FILE_LIBRARY_ACL=public-read 52 | FILE_LIBRARY_FILESIZE_LIMIT=50 53 | 54 | 55 | GLIDE_SOURCE= 56 | GLIDE_CACHE= 57 | GLIDE_CACHE_PATH_PREFIX=glide_cache 58 | GLIDE_BASE_URL= 59 | GLIDE_BASE_PATH=img 60 | GLIDE_USE_SIGNED_URLS=false 61 | GLIDE_SIGN_KEY= 62 | GLIDE_DRIVER=gd 63 | 64 | DEPLOY_REPOSITORY=git@github.com:code4romania/[CHANGEME].git 65 | 66 | DEPLOY_PRODUCTION_HOSTNAME= 67 | DEPLOY_PRODUCTION_PATH= 68 | 69 | DEPLOY_STAGING_HOSTNAME= 70 | DEPLOY_STAGING_PATH= 71 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # These owners will be the default owners for everything in 2 | # the repo. Unless a later match takes precedence, 3 | # they will be requested for review when someone 4 | # opens a pull request. 5 | * @andreiio 6 | 7 | # More details on creating a codeowners file: 8 | # https://help.github.com/en/github/creating-cloning-and-archiving-repositories/about-code-owners 9 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **To Reproduce** 14 | Steps to reproduce the behavior: 15 | 1. Go to '...' 16 | 2. Click on '....' 17 | 3. Scroll down to '....' 18 | 4. See error 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Screenshots** 24 | If applicable, add screenshots to help explain your problem. 25 | 26 | **Desktop (please complete the following information):** 27 | - OS: [e.g. iOS] 28 | - Browser [e.g. chrome, safari] 29 | - Version [e.g. 22] 30 | 31 | **Smartphone (please complete the following information):** 32 | - Device: [e.g. iPhone6] 33 | - OS: [e.g. iOS8.1] 34 | - Browser [e.g. stock browser, safari] 35 | - Version [e.g. 22] 36 | 37 | **Additional context** 38 | Add any other context about the problem here. 39 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | 3 | on: [push] 4 | 5 | jobs: 6 | test: 7 | name: PHP ${{ matrix.php-version }} - ${{ matrix.dependency-version }} 8 | runs-on: ubuntu-latest 9 | strategy: 10 | fail-fast: false 11 | matrix: 12 | php-version: 13 | - 7.4 14 | dependency-version: 15 | # - prefer-lowest 16 | - prefer-stable 17 | 18 | services: 19 | mysql: 20 | image: mariadb:10.4 21 | env: 22 | MYSQL_ALLOW_EMPTY_PASSWORD: yes 23 | MYSQL_DATABASE: code4_cms_tests 24 | ports: 25 | - 3306 26 | options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 27 | 28 | steps: 29 | - name: Checkout code 30 | uses: actions/checkout@v2 31 | 32 | - name: Setup PHP 33 | uses: shivammathur/setup-php@v2 34 | with: 35 | php-version: ${{ matrix.php-version }} 36 | extensions: mbstring, sqlite, pdo_sqlite 37 | coverage: none 38 | 39 | - name: Cache dependencies 40 | uses: actions/cache@v1 41 | with: 42 | path: ~/.composer/cache/files 43 | key: dependencies-php-${{ matrix.php-version }}-composer-${{ hashFiles('composer.json') }} 44 | 45 | - name: Install dependencies 46 | run: composer update --${{ matrix.dependency-version }} --prefer-dist --no-interaction --no-suggest 47 | 48 | - name: Run tests 49 | run: composer test 50 | env: 51 | DB_PORT: ${{ job.services.mysql.ports[3306] }} 52 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /public 2 | /vendor 3 | /node_modules 4 | /coverage 5 | .phpunit.result.cache 6 | package-lock.json 7 | composer.lock 8 | -------------------------------------------------------------------------------- /.gitignore.dist: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /public/hot 3 | /public/storage 4 | /public/assets 5 | /public/mix-manifest.json 6 | /storage/*.key 7 | /vendor 8 | .env 9 | .env.backup 10 | .phpunit.result.cache 11 | Homestead.json 12 | Homestead.yaml 13 | npm-debug.log 14 | yarn-error.log 15 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "printWidth": 80, 4 | "tabWidth": 4, 5 | "useTabs": false, 6 | "semi": true, 7 | "singleQuote": true, 8 | "overrides": [ 9 | { 10 | "files": ["*.yml", "*.yaml"], 11 | "options": { 12 | "tabWidth": 2 13 | } 14 | }, 15 | { 16 | "files": ["*.php"], 17 | "options": { 18 | "printWidth": 120 19 | } 20 | } 21 | ] 22 | } 23 | -------------------------------------------------------------------------------- /.styleci.yml: -------------------------------------------------------------------------------- 1 | preset: psr2 2 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "i18n-ally.localesPaths": "resources/lang" 3 | } 4 | -------------------------------------------------------------------------------- /config/analytics.php: -------------------------------------------------------------------------------- 1 | env('ANALYTICS_TRACKING_ID'), 9 | 10 | /* 11 | * The view id of which you want to display data. 12 | */ 13 | 'view_id' => env('ANALYTICS_VIEW_ID'), 14 | 15 | /* 16 | * Path to the client secret json file. Take a look at the README of this package 17 | * to learn how to get this file. You can also pass the credentials as an array 18 | * instead of a file path. 19 | */ 20 | 'service_account_credentials_json' => storage_path('app/analytics/service-account-credentials.json'), 21 | 22 | /* 23 | * The amount of minutes the Google API responses will be cached. 24 | * If you set this to zero, the responses won't be cached at all. 25 | */ 26 | 'cache_lifetime_in_minutes' => 60 * 24, 27 | 28 | /* 29 | * Here you may configure the "store" that the underlying Google_Client will 30 | * use to store it's data. You may also add extra parameters that will 31 | * be passed on setCacheConfig (see docs for google-api-php-client). 32 | * 33 | * Optional parameters: "lifetime", "prefix" 34 | */ 35 | 'cache' => [ 36 | 'store' => 'file', 37 | ], 38 | ]; 39 | -------------------------------------------------------------------------------- /config/cms.php: -------------------------------------------------------------------------------- 1 | [ 8 | 'primary', 9 | 'warning', 10 | 'danger', 11 | 'success', 12 | 'black', 13 | 'gray', 14 | 'none', 15 | ] 16 | 17 | ]; 18 | -------------------------------------------------------------------------------- /config/cms/editor.php: -------------------------------------------------------------------------------- 1 | 'tiptap', // 'tiptap' or 'quill' 8 | 'toolbar' => [ 9 | ['header' => [2, 3, false]], 10 | 'bold', 'italic', 'underline', 'strike', 'link', 'blockquote', 11 | 'list', 12 | 13 | ['list' => 'ordered'], 14 | ['list' => 'bullet'], 15 | ['script' => 'sub'], 16 | ['script' => 'super'], 17 | ['align' => []], 18 | 'table', 19 | ], 20 | 21 | ]; 22 | -------------------------------------------------------------------------------- /config/cms/embeds.php: -------------------------------------------------------------------------------- 1 | 60 * 60 * 24 * 30, 9 | 10 | 'args' => [ 11 | // When set to true, this chooses the bigger image as the main image 12 | // instead the first found, that usually is the most relevant. 13 | 'choose_bigger_image' => true, 14 | ], 15 | 16 | // This should match the aspect raio values defined in tailwind.config.js 17 | 'aspectRatio' => [ 18 | '1/1' => 1 / 1, 19 | '5/4' => 5 / 4, 20 | '4/3' => 4 / 3, 21 | '3/2' => 3 / 2, 22 | '5/3' => 5 / 3, 23 | '16/9' => 16 / 9, 24 | '2/1' => 2 / 1, 25 | '3/1' => 3 / 1, 26 | '5/6' => 5 / 6, 27 | '4/5' => 4 / 5, 28 | '3/4' => 3 / 4, 29 | '2/3' => 2 / 3, 30 | '3/5' => 3 / 5, 31 | '9/16' => 9 / 16, 32 | '1/2' => 1 / 2, 33 | '1/3' => 1 / 3, 34 | ], 35 | 36 | ]; 37 | -------------------------------------------------------------------------------- /config/cms/enabled.php: -------------------------------------------------------------------------------- 1 | true, 8 | 9 | ]; 10 | -------------------------------------------------------------------------------- /config/cms/form.php: -------------------------------------------------------------------------------- 1 | [ 8 | 'text', 9 | 'textarea', 10 | 'url', 11 | 'email', 12 | 'number', 13 | 'date', 14 | 'file', 15 | 'select', 16 | 'radio', 17 | 'checkbox', 18 | ], 19 | 20 | ]; 21 | -------------------------------------------------------------------------------- /config/cms/menu.php: -------------------------------------------------------------------------------- 1 | [ 8 | 'header', 9 | 'footer', 10 | ], 11 | 12 | 'itemTypes' => [ 13 | 'external', 14 | 'page', 15 | 'category', 16 | 'blog', 17 | ], 18 | 19 | ]; 20 | -------------------------------------------------------------------------------- /config/cms/social.php: -------------------------------------------------------------------------------- 1 | [ 9 | 'facebook' => [ 10 | 'name' => 'Facebook', 11 | 'baseUrl' => 'https://www.facebook.com/', 12 | ], 13 | 'twitter' => [ 14 | 'name' => 'Twitter', 15 | 'baseUrl' => 'https://twitter.com/', 16 | ], 17 | 'instagram' => [ 18 | 'name' => 'Instagram', 19 | 'baseUrl' => 'https://www.instagram.com/', 20 | ], 21 | 'github' => [ 22 | 'name' => 'GitHub', 23 | 'baseUrl' => 'https://github.com/', 24 | ], 25 | 'linkedin' => [ 26 | 'name' => 'LinkedIn', 27 | 'baseUrl' => 'https://www.linkedin.com/', 28 | ], 29 | 'tiktok' => [ 30 | 'name' => 'TikTok', 31 | 'baseUrl' => 'https://www.tiktok.com/@', 32 | ], 33 | ], 34 | 35 | ]; 36 | -------------------------------------------------------------------------------- /config/twill.php: -------------------------------------------------------------------------------- 1 | 'Code4Romania\Cms', 8 | 9 | 'admin_app_url' => env('ADMIN_APP_URL', env('APP_URL', 'http://localhost')), 10 | 'admin_app_path' => env('ADMIN_APP_PATH', 'admin'), 11 | 12 | 'migrations_use_big_integers' => true, 13 | 'load_default_migrations_from_twill' => true, 14 | 15 | 'enabled' => [ 16 | 17 | 'users-management' => true, 18 | 'media-library' => true, 19 | 'file-library' => true, 20 | 'dashboard' => true, 21 | 'search' => true, 22 | 'block-editor' => true, 23 | 'buckets' => false, 24 | 'users-image' => false, 25 | 'users-description' => false, 26 | 'site-link' => false, 27 | 'settings' => true, 28 | 'activitylog' => true, 29 | 30 | ], 31 | 32 | ]; 33 | -------------------------------------------------------------------------------- /config/twill/block_editor.php: -------------------------------------------------------------------------------- 1 | 'front.layouts.blocks', 9 | 10 | // Path where a view file per block type is stored 11 | 'block_views_path' => 'front.blocks', 12 | 13 | // Custom mapping of block types and views 14 | 'block_views_mappings' => [], 15 | 16 | // Indicates if childs should be rendered when using repeater in blocks 17 | 'block_preview_render_childs' => false, 18 | 19 | // Allows setting a custom presenter to a block model 20 | 'block_presenter_path' => Code4Romania\Cms\Presenters\BlockPresenter::class, 21 | 22 | // 23 | 'inline_blocks_templates' => true, 24 | 25 | // Use builtin twill blocks 26 | 'use_twill_blocks' => false, 27 | 28 | 29 | 'directories' => [ 30 | 'source' => [ 31 | 'blocks' => [ 32 | [ 33 | 'path' => resource_path('views/admin/blocks'), 34 | 'source' => A17\Twill\Services\Blocks\Block::SOURCE_APP, 35 | ], 36 | ], 37 | 38 | 'repeaters' => [ 39 | [ 40 | 'path' => resource_path('views/admin/repeaters'), 41 | 'source' => A17\Twill\Services\Blocks\Block::SOURCE_APP, 42 | ], 43 | ], 44 | 45 | 'icons' => [ 46 | base_path('vendor/area17/twill/frontend/icons'), 47 | resource_path('assets/svg/icons'), 48 | ], 49 | ], 50 | 51 | 'destination' => [ 52 | 'make_dir' => true, 53 | 54 | 'blocks' => resource_path('views/admin/blocks'), 55 | 56 | 'repeaters' => resource_path('views/admin/repeaters'), 57 | ], 58 | ], 59 | ]; 60 | -------------------------------------------------------------------------------- /config/twill/dashboard.php: -------------------------------------------------------------------------------- 1 | [ 6 | 'enabled' => boolval(env('ANALYTICS_VIEW_ID')), 7 | 'service_account_credentials_json' => storage_path('app/analytics/service-account-credentials.json'), 8 | ], 9 | 10 | 'modules' => [ 11 | /** 12 | * @param array $module Array containing the module config params. 13 | * 14 | * 'moduleName' = [ 15 | * 'name' => (string) [required] Module name 16 | * 'label' => (string) [optional] If the name of your module above does not work as a label 17 | * 'label_singular' => (string) [optional] If the automated singular version does not work as a label 18 | * 'routePrefix' => (string) [optional] If the module is living under a specific routes group 19 | * 'count' => (boolean) [required] Show total count with link to index of this module 20 | * 'create' => (boolean) [required] Show link in create new dropdown 21 | * 'activity' => (boolean) [required] Show activities on this module in actities list 22 | * 'draft => (boolean) [required] Show drafts of this module for current user 23 | * 'search' => (boolean) [required] Show results for this module in global search 24 | * ] 25 | */ 26 | 27 | 'page' => [ 28 | 'name' => 'pages', 29 | 'label' => 'Pages', 30 | 'label_singular' => 'Page', 31 | 'routePrefix' => '', 32 | 'count' => true, 33 | 'create' => true, 34 | 'activity' => true, 35 | 'draft' => true, 36 | 'search' => true, 37 | ], 38 | 39 | ], 40 | ]; 41 | -------------------------------------------------------------------------------- /config/twill/file_library.php: -------------------------------------------------------------------------------- 1 | 'twill_file_library', 22 | 'endpoint_type' => env('FILE_LIBRARY_ENDPOINT_TYPE', 'local'), 23 | 'cascade_delete' => env('FILE_LIBRARY_CASCADE_DELETE', true), 24 | 'local_path' => env('FILE_LIBRARY_LOCAL_PATH', 'uploads'), 25 | 'file_service' => env('FILE_LIBRARY_FILE_SERVICE', 'A17\Twill\Services\FileLibrary\Disk'), 26 | 'acl' => env('FILE_LIBRARY_ACL', 'public-read'), 27 | 'filesize_limit' => env('FILE_LIBRARY_FILESIZE_LIMIT', 50), 28 | 'allowed_extensions' => [], 29 | 'prefix_uuid_with_local_path' => false, 30 | ]; 31 | -------------------------------------------------------------------------------- /config/twill/glide.php: -------------------------------------------------------------------------------- 1 | env('GLIDE_SOURCE', storage_path('app/public/' . env('MEDIA_LIBRARY_LOCAL_PATH', 'uploads'))), 15 | 'cache' => env('GLIDE_CACHE', storage_path('app')), 16 | 'cache_path_prefix' => env('GLIDE_CACHE_PATH_PREFIX', 'glide_cache'), 17 | 'base_url' => env('GLIDE_BASE_URL', env('APP_URL', 'http://localhost')), 18 | 'base_path' => env('GLIDE_BASE_PATH', 'img'), 19 | 'use_signed_urls' => env('GLIDE_USE_SIGNED_URLS', false), 20 | 'sign_key' => env('GLIDE_SIGN_KEY'), 21 | 'driver' => env('GLIDE_DRIVER', 'gd'), 22 | 'add_params_to_svgs' => false, 23 | 24 | 'default_params' => [ 25 | // 'fm' => 'jpg', 26 | 'q' => '80', 27 | 'fit' => 'max', 28 | ], 29 | 30 | 'lqip_default_params' => [ 31 | 'fm' => 'png', 32 | 'blur' => 10, 33 | 'dpr' => 1, 34 | ], 35 | 36 | 'social_default_params' => [ 37 | 'fm' => 'jpg', 38 | 'w' => 1200, 39 | 'h' => 630, 40 | 'fit' => 'crop', 41 | ], 42 | 43 | 'cms_default_params' => [ 44 | 'q' => '60', 45 | 'dpr' => '1', 46 | ], 47 | 48 | 'presets' => [ 49 | // 50 | ], 51 | ]; 52 | -------------------------------------------------------------------------------- /config/twill/media_library.php: -------------------------------------------------------------------------------- 1 | 'twill_media_library', 23 | 'endpoint_type' => env('MEDIA_LIBRARY_ENDPOINT_TYPE', 'local'), 24 | 'cascade_delete' => env('MEDIA_LIBRARY_CASCADE_DELETE', true), 25 | 'local_path' => env('MEDIA_LIBRARY_LOCAL_PATH', 'uploads'), 26 | 'image_service' => env('MEDIA_LIBRARY_IMAGE_SERVICE', 'A17\Twill\Services\MediaLibrary\Glide'), 27 | 'acl' => env('MEDIA_LIBRARY_ACL', 'private'), 28 | 'filesize_limit' => env('MEDIA_LIBRARY_FILESIZE_LIMIT', 50), 29 | 'allowed_extensions' => ['jpg', 'gif', 'png', 'jpeg'], 30 | 'init_alt_text_from_filename' => true, 31 | 'prefix_uuid_with_local_path' => config('twill.file_library.prefix_uuid_with_local_path', false), 32 | 'translated_form_fields' => false, 33 | ]; 34 | -------------------------------------------------------------------------------- /database/factories/BlockFactory.php: -------------------------------------------------------------------------------- 1 | define(Block::class, static function (Faker $faker): array { 11 | return [ 12 | 'type' => null, 13 | 'position' => $faker->randomDigitNotNull, 14 | 'content' => [], 15 | ]; 16 | }); 17 | -------------------------------------------------------------------------------- /database/factories/FormFactory.php: -------------------------------------------------------------------------------- 1 | define(Form::class, static function (Faker $faker): array { 12 | return [ 13 | 'published' => false, 14 | 'store' => false, 15 | 'send' => false, 16 | 'confirm' => false, 17 | 'recipients' => null, 18 | ]; 19 | }); 20 | 21 | $factory->state(Form::class, 'published', [ 22 | 'published' => true, 23 | ]); 24 | 25 | $factory->define(FormTranslation::class, static function (Faker $faker): array { 26 | return [ 27 | 'form_id' => null, 28 | 'title' => $faker->sentence, 29 | 'locale' => 'en', 30 | 'active' => false, 31 | ]; 32 | }); 33 | 34 | $factory->state(FormTranslation::class, 'active', [ 35 | 'active' => true, 36 | ]); 37 | 38 | $factory->afterCreating(Form::class, static function (Form $form, Faker $faker): void { 39 | $locales = collect(config('translatable.locales')); 40 | 41 | $form->translation()->saveMany( 42 | $locales->map(function ($locale) use ($form) { 43 | return factory(FormTranslation::class) 44 | ->state('active') 45 | ->create([ 46 | 'form_id' => $form->id, 47 | 'locale' => $locale, 48 | ]); 49 | }) 50 | ); 51 | 52 | // we need to manually "reload" the collection built from the relationship 53 | // otherwise $this->translations()->get() would NOT be the same as $this->translations 54 | $form->load('translations'); 55 | }); 56 | -------------------------------------------------------------------------------- /database/factories/MediaFactory.php: -------------------------------------------------------------------------------- 1 | define(Media::class, static function (Faker $faker): array { 11 | $uuid = $faker->uuid; 12 | $width = $faker->numberBetween(500, 1000); 13 | $height = $faker->numberBetween(500, 1000); 14 | $filename = $faker->image(storage_path("app/public/uploads/{$uuid}"), $width, $height, false); 15 | 16 | return [ 17 | 'uuid' => $uuid . '/' . $filename, 18 | 'filename' => $filename, 19 | 'width' => $width, 20 | 'height' => $height, 21 | 'alt_text' => $faker->sentence, 22 | 'caption' => null, 23 | ]; 24 | }); 25 | -------------------------------------------------------------------------------- /database/factories/MenuFactory.php: -------------------------------------------------------------------------------- 1 | define(Menu::class, static function (Faker $faker): array { 12 | return [ 13 | 'published' => false, 14 | 'location' => null, 15 | ]; 16 | }); 17 | 18 | 19 | $factory->state(Menu::class, 'published', [ 20 | 'published' => true, 21 | ]); 22 | 23 | $factory->state(Menu::class, 'header', [ 24 | 'location' => 'header', 25 | ]); 26 | 27 | $factory->state(Menu::class, 'footer', [ 28 | 'location' => 'footer', 29 | ]); 30 | 31 | 32 | $factory->state(MenuTranslation::class, 'active', [ 33 | 'active' => true, 34 | ]); 35 | 36 | $factory->define(MenuTranslation::class, static function (Faker $faker): array { 37 | return []; 38 | }); 39 | 40 | $factory->afterCreating(Menu::class, static function (Menu $menu, Faker $faker) { 41 | $locales = collect(config('translatable.locales')); 42 | 43 | $menu->translation()->saveMany( 44 | $locales->map(function ($locale) use ($menu) { 45 | return factory(MenuTranslation::class) 46 | ->state('active') 47 | ->create([ 48 | 'menu_id' => $menu->id, 49 | 'locale' => $locale, 50 | ]); 51 | }) 52 | ); 53 | 54 | // we need to manually "reload" the collection built from the relationship 55 | // otherwise $this->translations()->get() would NOT be the same as $this->translations 56 | $menu->load('translations'); 57 | }); 58 | -------------------------------------------------------------------------------- /database/factories/PartnerFactory.php: -------------------------------------------------------------------------------- 1 | define(Partner::class, static function (Faker $faker): array { 11 | return [ 12 | 'published' => false, 13 | 'name' => $faker->name, 14 | 'website' => $faker->boolean(75) ? 'https://' . $faker->safeEmailDomain : null, 15 | ]; 16 | }); 17 | 18 | 19 | $factory->state(Partner::class, 'published', [ 20 | 'published' => true, 21 | ]); 22 | -------------------------------------------------------------------------------- /database/factories/PersonFactory.php: -------------------------------------------------------------------------------- 1 | define(Person::class, static function (Faker $faker): array { 12 | return [ 13 | 'published' => false, 14 | 'name' => $faker->name, 15 | ]; 16 | }); 17 | 18 | 19 | $factory->state(Person::class, 'published', [ 20 | 'published' => true, 21 | ]); 22 | 23 | 24 | $factory->state(PersonTranslation::class, 'active', [ 25 | 'active' => true, 26 | ]); 27 | 28 | $factory->define(PersonTranslation::class, static function (Faker $faker): array { 29 | return []; 30 | }); 31 | 32 | $factory->afterCreating(Person::class, static function (Person $person, Faker $faker) { 33 | $locales = collect(config('translatable.locales')); 34 | 35 | $person->translation()->saveMany( 36 | $locales->map(function ($locale) use ($person) { 37 | return factory(PersonTranslation::class) 38 | ->state('active') 39 | ->create([ 40 | 'person_id' => $person->id, 41 | 'locale' => $locale, 42 | ]); 43 | }) 44 | ); 45 | 46 | // we need to manually "reload" the collection built from the relationship 47 | // otherwise $this->translations()->get() would NOT be the same as $this->translations 48 | $person->load('translations'); 49 | }); 50 | -------------------------------------------------------------------------------- /database/factories/ResponseFactory.php: -------------------------------------------------------------------------------- 1 | define(Response::class, static function (Faker $faker): array { 12 | return [ 13 | 'form_id' => factory(Form::class), 14 | 'data' => collect(range(0, $faker->randomDigitNotNull)) 15 | ->map(function () use ($faker) { 16 | return [ 17 | 'title' => $faker->sentence, 18 | 'fields' => collect(range(0, $faker->randomDigitNotNull)) 19 | ->map(function () use ($faker) { 20 | return [ 21 | 'label' => $faker->sentence, 22 | 'value' => $faker->word, 23 | ]; 24 | }), 25 | ]; 26 | }), 27 | ]; 28 | }); 29 | -------------------------------------------------------------------------------- /database/factories/UserFactory.php: -------------------------------------------------------------------------------- 1 | define(User::class, static function (Faker $faker): array { 11 | return [ 12 | 'published' => false, 13 | 'name' => $faker->name, 14 | 'email' => $faker->safeEmail, 15 | 'password' => '$2y$10$92IXUNpkjO0rOQ5byMi.Ye4oKoEa3Ro9llC/.og/at2.uheWG/igi', // password 16 | 'role' => 'VIEWONLY', 17 | ]; 18 | }); 19 | 20 | $factory->state(User::class, 'active', [ 21 | 'published' => true, 22 | ]); 23 | 24 | $factory->state(User::class, 'admin', [ 25 | 'role' => 'ADMIN', 26 | ]); 27 | 28 | $factory->state(User::class, 'publisher', [ 29 | 'role' => 'PUBLISHER', 30 | ]); 31 | -------------------------------------------------------------------------------- /database/migrations/default/2020_02_10_000001_create_pages_tables.php: -------------------------------------------------------------------------------- 1 | integer('position')->unsigned()->nullable(); 17 | 18 | // enable publication timeframe fields 19 | $table->timestamp('publish_start_date')->nullable(); 20 | $table->timestamp('publish_end_date')->nullable(); 21 | 22 | $table->boolean('show_header')->default(true); 23 | }); 24 | 25 | Schema::create('page_translations', static function (Blueprint $table): void { 26 | createDefaultTranslationsTableFields($table, 'page'); 27 | $table->string('title', 200)->nullable(); 28 | $table->text('description')->nullable(); 29 | }); 30 | 31 | Schema::create('page_slugs', static function (Blueprint $table): void { 32 | createDefaultSlugsTableFields($table, 'page'); 33 | }); 34 | 35 | Schema::create('page_revisions', static function (Blueprint $table): void { 36 | createDefaultRevisionsTableFields($table, 'page'); 37 | }); 38 | } 39 | 40 | public function down(): void 41 | { 42 | Schema::dropIfExists('page_revisions'); 43 | Schema::dropIfExists('page_translations'); 44 | Schema::dropIfExists('page_slugs'); 45 | Schema::dropIfExists('pages'); 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /database/migrations/default/2020_02_10_000002_create_menus_tables.php: -------------------------------------------------------------------------------- 1 | string('location')->nullable(); 16 | }); 17 | 18 | Schema::create('menu_translations', static function (Blueprint $table): void { 19 | createDefaultTranslationsTableFields($table, 'menu'); 20 | $table->string('title')->nullable(); 21 | }); 22 | } 23 | 24 | public function down(): void 25 | { 26 | Schema::dropIfExists('menu_translations'); 27 | Schema::dropIfExists('menus'); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /database/migrations/default/2020_02_10_000003_create_forms_tables.php: -------------------------------------------------------------------------------- 1 | boolean('store')->default(false); 16 | $table->boolean('send')->default(false); 17 | $table->boolean('confirm')->default(false); 18 | $table->text('recipients')->nullable(); 19 | }); 20 | 21 | Schema::create('form_translations', static function (Blueprint $table): void { 22 | createDefaultTranslationsTableFields($table, 'form'); 23 | $table->string('title')->nullable(); 24 | }); 25 | } 26 | 27 | public function down(): void 28 | { 29 | Schema::dropIfExists('form_translations'); 30 | Schema::dropIfExists('forms'); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /database/migrations/default/2020_02_10_000004_create_responses_tables.php: -------------------------------------------------------------------------------- 1 | json('data')->nullable(); 16 | $table->foreignId('form_id')->constrained(); 17 | }); 18 | } 19 | 20 | public function down(): void 21 | { 22 | Schema::dropIfExists('responses'); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /database/migrations/default/2020_02_10_000005_create_partners_tables.php: -------------------------------------------------------------------------------- 1 | string('name', 200)->nullable(); 17 | $table->string('website', 500)->nullable(); 18 | 19 | $table->integer('position')->unsigned()->nullable(); 20 | }); 21 | } 22 | 23 | public function down() 24 | { 25 | Schema::dropIfExists('partners'); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /database/migrations/default/2020_02_10_000006_create_posts_tables.php: -------------------------------------------------------------------------------- 1 | string('author', 200)->nullable(); 17 | 18 | // add those 2 columns to enable publication timeframe fields (you can use publish_start_date only if you don't need to provide the ability to specify an end date) 19 | $table->timestamp('publish_start_date')->nullable(); 20 | $table->timestamp('publish_end_date')->nullable(); 21 | }); 22 | 23 | Schema::create('post_translations', static function (Blueprint $table): void { 24 | createDefaultTranslationsTableFields($table, 'post'); 25 | $table->string('title', 200)->nullable(); 26 | $table->text('description')->nullable(); 27 | }); 28 | 29 | Schema::create('post_slugs', static function (Blueprint $table): void { 30 | createDefaultSlugsTableFields($table, 'post'); 31 | }); 32 | 33 | Schema::create('post_revisions', static function (Blueprint $table): void { 34 | createDefaultRevisionsTableFields($table, 'post'); 35 | }); 36 | } 37 | 38 | public function down() 39 | { 40 | Schema::dropIfExists('post_revisions'); 41 | Schema::dropIfExists('post_translations'); 42 | Schema::dropIfExists('post_slugs'); 43 | Schema::dropIfExists('posts'); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /database/migrations/default/2020_02_10_000007_create_categories_tables.php: -------------------------------------------------------------------------------- 1 | string('title', 200)->nullable(); 20 | $table->text('description')->nullable(); 21 | }); 22 | 23 | Schema::create('category_slugs', static function (Blueprint $table): void { 24 | createDefaultSlugsTableFields($table, 'category'); 25 | }); 26 | 27 | Schema::create('category_post', static function (Blueprint $table): void { 28 | $table->{twillIncrementsMethod()}('id'); 29 | $table->integer('position')->unsigned()->nullable(); 30 | createDefaultRelationshipTableFields($table, 'category', 'post'); 31 | }); 32 | } 33 | 34 | public function down() 35 | { 36 | Schema::dropIfExists('category_post'); 37 | Schema::dropIfExists('category_translations'); 38 | Schema::dropIfExists('category_slugs'); 39 | Schema::dropIfExists('categories'); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /database/migrations/people/2020_05_04_000001_create_city_labs_tables.php: -------------------------------------------------------------------------------- 1 | string('name', 200)->nullable(); 20 | $table->text('description')->nullable(); 21 | }); 22 | 23 | Schema::create('city_lab_slugs', static function (Blueprint $table): void { 24 | createDefaultSlugsTableFields($table, 'city_lab'); 25 | }); 26 | 27 | Schema::create('city_lab_revisions', static function (Blueprint $table): void { 28 | createDefaultRevisionsTableFields($table, 'city_lab'); 29 | }); 30 | } 31 | 32 | public function down() 33 | { 34 | Schema::dropIfExists('city_lab_revisions'); 35 | Schema::dropIfExists('city_lab_translations'); 36 | Schema::dropIfExists('city_lab_slugs'); 37 | Schema::dropIfExists('city_labs'); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /database/migrations/people/2020_05_04_000002_create_people_tables.php: -------------------------------------------------------------------------------- 1 | string('name', 200); 17 | $table->string('linkedin', 200)->nullable(); 18 | $table->string('github', 200)->nullable(); 19 | }); 20 | 21 | Schema::create('person_translations', static function (Blueprint $table): void { 22 | createDefaultTranslationsTableFields($table, 'person'); 23 | $table->string('title', 200)->nullable(); 24 | $table->text('description')->nullable(); 25 | }); 26 | 27 | Schema::create('personables', function (Blueprint $table) { 28 | $table->increments('id'); 29 | $table->{twillIntegerMethod()}('position')->unsigned()->nullable(); 30 | $table->{twillIntegerMethod()}('personable_id')->nullable()->unsigned(); 31 | $table->string('personable_type')->nullable(); 32 | $table->{twillIntegerMethod()}('person_id')->unsigned(); 33 | $table->foreign('person_id', 'fk_personables_person_id')->references('id')->on('people')->onDelete('cascade')->onUpdate('cascade'); 34 | $table->index(['personable_type', 'personable_id']); 35 | }); 36 | } 37 | 38 | public function down() 39 | { 40 | Schema::dropIfExists('personables'); 41 | Schema::dropIfExists('person_translations'); 42 | Schema::dropIfExists('people'); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /deploy/assets.php: -------------------------------------------------------------------------------- 1 | local(); 15 | 16 | desc('Build frontend assets locally'); 17 | task('assets:build', function () { 18 | if (test('[ -d public/assets ]')) { 19 | return; 20 | } 21 | 22 | run('{{bin/npm}} run production'); 23 | })->local(); 24 | 25 | desc('Upload assets to your hosts'); 26 | task('assets:upload', function () { 27 | upload('public/assets/', '{{release_path}}/public/assets/'); 28 | }); 29 | -------------------------------------------------------------------------------- /phpcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | src 8 | tests 9 | 10 | migrations/* 11 | seeds/* 12 | vendor/* 13 | *.blade.php 14 | *.js 15 | 16 | 17 | 18 | 19 | -------------------------------------------------------------------------------- /phpstan.neon: -------------------------------------------------------------------------------- 1 | includes: 2 | - ./vendor/nunomaduro/larastan/extension.neon 3 | 4 | parameters: 5 | 6 | paths: 7 | - src 8 | 9 | # The level 8 is the highest level 10 | level: 5 11 | 12 | ignoreErrors: 13 | - '#Unsafe usage of new static#' 14 | 15 | excludes_analyse: 16 | - ./*/*/FileToBeExcluded.php 17 | 18 | # checkMissingIterableValueType: false 19 | -------------------------------------------------------------------------------- /phpunit.xml: -------------------------------------------------------------------------------- 1 | 2 | 15 | 16 | 17 | ./tests/Unit 18 | 19 | 20 | 21 | 22 | src 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | -------------------------------------------------------------------------------- /resources/assets/css/app.pcss: -------------------------------------------------------------------------------- 1 | @import 'tailwindcss/base'; 2 | @import 'tailwindcss/components'; 3 | @import 'tailwindcss/utilities'; 4 | 5 | @import 'fonts.pcss'; 6 | @import 'content.pcss'; 7 | 8 | [x-cloak] { 9 | display: none; 10 | } 11 | -------------------------------------------------------------------------------- /resources/assets/fonts/titillium-web-latin-ext/200-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code4romania/cms/c83a48b5902a7b2fafbb56abd38df4d95a1c1883/resources/assets/fonts/titillium-web-latin-ext/200-italic.woff -------------------------------------------------------------------------------- /resources/assets/fonts/titillium-web-latin-ext/200-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code4romania/cms/c83a48b5902a7b2fafbb56abd38df4d95a1c1883/resources/assets/fonts/titillium-web-latin-ext/200-italic.woff2 -------------------------------------------------------------------------------- /resources/assets/fonts/titillium-web-latin-ext/200.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code4romania/cms/c83a48b5902a7b2fafbb56abd38df4d95a1c1883/resources/assets/fonts/titillium-web-latin-ext/200.woff -------------------------------------------------------------------------------- /resources/assets/fonts/titillium-web-latin-ext/200.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code4romania/cms/c83a48b5902a7b2fafbb56abd38df4d95a1c1883/resources/assets/fonts/titillium-web-latin-ext/200.woff2 -------------------------------------------------------------------------------- /resources/assets/fonts/titillium-web-latin-ext/300-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code4romania/cms/c83a48b5902a7b2fafbb56abd38df4d95a1c1883/resources/assets/fonts/titillium-web-latin-ext/300-italic.woff -------------------------------------------------------------------------------- /resources/assets/fonts/titillium-web-latin-ext/300-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code4romania/cms/c83a48b5902a7b2fafbb56abd38df4d95a1c1883/resources/assets/fonts/titillium-web-latin-ext/300-italic.woff2 -------------------------------------------------------------------------------- /resources/assets/fonts/titillium-web-latin-ext/300.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code4romania/cms/c83a48b5902a7b2fafbb56abd38df4d95a1c1883/resources/assets/fonts/titillium-web-latin-ext/300.woff -------------------------------------------------------------------------------- /resources/assets/fonts/titillium-web-latin-ext/300.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code4romania/cms/c83a48b5902a7b2fafbb56abd38df4d95a1c1883/resources/assets/fonts/titillium-web-latin-ext/300.woff2 -------------------------------------------------------------------------------- /resources/assets/fonts/titillium-web-latin-ext/400-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code4romania/cms/c83a48b5902a7b2fafbb56abd38df4d95a1c1883/resources/assets/fonts/titillium-web-latin-ext/400-italic.woff -------------------------------------------------------------------------------- /resources/assets/fonts/titillium-web-latin-ext/400-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code4romania/cms/c83a48b5902a7b2fafbb56abd38df4d95a1c1883/resources/assets/fonts/titillium-web-latin-ext/400-italic.woff2 -------------------------------------------------------------------------------- /resources/assets/fonts/titillium-web-latin-ext/400.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code4romania/cms/c83a48b5902a7b2fafbb56abd38df4d95a1c1883/resources/assets/fonts/titillium-web-latin-ext/400.woff -------------------------------------------------------------------------------- /resources/assets/fonts/titillium-web-latin-ext/400.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code4romania/cms/c83a48b5902a7b2fafbb56abd38df4d95a1c1883/resources/assets/fonts/titillium-web-latin-ext/400.woff2 -------------------------------------------------------------------------------- /resources/assets/fonts/titillium-web-latin-ext/600-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code4romania/cms/c83a48b5902a7b2fafbb56abd38df4d95a1c1883/resources/assets/fonts/titillium-web-latin-ext/600-italic.woff -------------------------------------------------------------------------------- /resources/assets/fonts/titillium-web-latin-ext/600-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code4romania/cms/c83a48b5902a7b2fafbb56abd38df4d95a1c1883/resources/assets/fonts/titillium-web-latin-ext/600-italic.woff2 -------------------------------------------------------------------------------- /resources/assets/fonts/titillium-web-latin-ext/600.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code4romania/cms/c83a48b5902a7b2fafbb56abd38df4d95a1c1883/resources/assets/fonts/titillium-web-latin-ext/600.woff -------------------------------------------------------------------------------- /resources/assets/fonts/titillium-web-latin-ext/600.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code4romania/cms/c83a48b5902a7b2fafbb56abd38df4d95a1c1883/resources/assets/fonts/titillium-web-latin-ext/600.woff2 -------------------------------------------------------------------------------- /resources/assets/fonts/titillium-web-latin-ext/700-italic.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code4romania/cms/c83a48b5902a7b2fafbb56abd38df4d95a1c1883/resources/assets/fonts/titillium-web-latin-ext/700-italic.woff -------------------------------------------------------------------------------- /resources/assets/fonts/titillium-web-latin-ext/700-italic.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code4romania/cms/c83a48b5902a7b2fafbb56abd38df4d95a1c1883/resources/assets/fonts/titillium-web-latin-ext/700-italic.woff2 -------------------------------------------------------------------------------- /resources/assets/fonts/titillium-web-latin-ext/700.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code4romania/cms/c83a48b5902a7b2fafbb56abd38df4d95a1c1883/resources/assets/fonts/titillium-web-latin-ext/700.woff -------------------------------------------------------------------------------- /resources/assets/fonts/titillium-web-latin-ext/700.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code4romania/cms/c83a48b5902a7b2fafbb56abd38df4d95a1c1883/resources/assets/fonts/titillium-web-latin-ext/700.woff2 -------------------------------------------------------------------------------- /resources/assets/fonts/titillium-web-latin-ext/900.woff: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code4romania/cms/c83a48b5902a7b2fafbb56abd38df4d95a1c1883/resources/assets/fonts/titillium-web-latin-ext/900.woff -------------------------------------------------------------------------------- /resources/assets/fonts/titillium-web-latin-ext/900.woff2: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/code4romania/cms/c83a48b5902a7b2fafbb56abd38df4d95a1c1883/resources/assets/fonts/titillium-web-latin-ext/900.woff2 -------------------------------------------------------------------------------- /resources/assets/js/app.js: -------------------------------------------------------------------------------- 1 | import 'alpinejs'; 2 | import lazyload from '@area17/a17-lazyload'; 3 | 4 | document.addEventListener('DOMContentLoaded', () => lazyload()); 5 | -------------------------------------------------------------------------------- /resources/assets/js/components/.gitignore: -------------------------------------------------------------------------------- 1 | dist/ 2 | -------------------------------------------------------------------------------- /resources/assets/js/components/Ckeditor.vue: -------------------------------------------------------------------------------- 1 | /* eslint-disable */ 2 | 17 | 18 | 61 | -------------------------------------------------------------------------------- /resources/assets/svg/hex.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /resources/assets/svg/icons/author.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /resources/assets/svg/icons/calendar.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /resources/assets/svg/icons/camera-lens.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /resources/assets/svg/icons/camera.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /resources/assets/svg/icons/category.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /resources/assets/svg/icons/community.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /resources/assets/svg/icons/dropdown.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /resources/assets/svg/icons/facebook.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /resources/assets/svg/icons/github.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /resources/assets/svg/icons/global.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /resources/assets/svg/icons/instagram.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /resources/assets/svg/icons/linkedin.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /resources/assets/svg/icons/quote.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /resources/assets/svg/icons/tiktok.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /resources/assets/svg/icons/triangle-down.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /resources/assets/svg/icons/triangle-up.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /resources/assets/svg/icons/twitter.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /resources/assets/svg/logo-sm-gray.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /resources/assets/svg/logo-sm.svg: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /resources/lang/en/date.php: -------------------------------------------------------------------------------- 1 | 'MMMM Do YYYY', 6 | 7 | ]; 8 | -------------------------------------------------------------------------------- /resources/lang/en/email.php: -------------------------------------------------------------------------------- 1 | [ 6 | 'subject' => 'Form something something', 7 | ], 8 | 9 | ]; 10 | -------------------------------------------------------------------------------- /resources/lang/en/errors.php: -------------------------------------------------------------------------------- 1 | 'Back to the main page', 6 | '401' => 'Sorry, you are not authorized to access this page.', 7 | '403' => 'Sorry, you are forbidden from accessing this page.', 8 | '404' => 'Sorry, the page you are looking for could not be found.', 9 | '419' => 'Sorry, your session has expired. Please refresh and try again.', 10 | '429' => 'Sorry, you are making too many requests to our servers.', 11 | '500' => 'Whoops, something went wrong on our servers.', 12 | '503' => 'Sorry, we are doing some maintenance. Please check back soon.', 13 | 14 | ]; 15 | -------------------------------------------------------------------------------- /resources/lang/en/footer.php: -------------------------------------------------------------------------------- 1 | 'A project by', 6 | 'tagline' => 'An independent, non-partisan, non-political, non-govermental organization.', 7 | 8 | ]; 9 | -------------------------------------------------------------------------------- /resources/lang/en/misc.php: -------------------------------------------------------------------------------- 1 | 'Community', 6 | 7 | ]; 8 | -------------------------------------------------------------------------------- /resources/lang/en/pagination.php: -------------------------------------------------------------------------------- 1 | '← Previous', 17 | 'next' => 'Next →', 18 | 'page' => 'Page :page', 19 | 'label' => 'Pagination Navigation', 20 | 'goto' => 'Go to page :page', 21 | ]; 22 | -------------------------------------------------------------------------------- /resources/lang/hu/errors.php: -------------------------------------------------------------------------------- 1 | 'Vissza az első oldalra', 6 | '401' => 'Sajnáljuk, nem vagy azonosítva hogy ide belépj', 7 | '403' => 'Sajnáljuk, nem vagy jogosult hogy ide belépj', 8 | '404' => 'Sajnáljuk, nem találtuk meg a keresett oldalt.', 9 | '419' => 'Sajnos a munkamenet lejárt. Kérjük próbáld újra.', 10 | '429' => 'Sajnáljuk, túl sok kérést küldesz szervereinkhez.', 11 | '500' => 'Hoppá, valami nem ment jól a szervereinken.', 12 | '503' => 'Sajnáljuk, a webhely karbantartásán fáradozunk. Kérjük, próbáld újra később.', 13 | 14 | ]; 15 | -------------------------------------------------------------------------------- /resources/lang/ro/date.php: -------------------------------------------------------------------------------- 1 | 'DD MMMM YYYY', 6 | 7 | ]; 8 | -------------------------------------------------------------------------------- /resources/lang/ro/errors.php: -------------------------------------------------------------------------------- 1 | 'Înapoi la prima pagină', 6 | '401' => 'Ne pare rău, nu ești autorizat să accesezi această pagină.', 7 | '403' => 'Ne pare rău, nu ai dreptul să accesezi această pagină.', 8 | '404' => 'Ne pare rău, nu am găsit pagina pe care o cauți.', 9 | '419' => 'Ne pare rău, sesiunea ta a expirat. Te rugăm să încerci din nou.', 10 | '429' => 'Ne pare rău, faci prea multe request-uri către serverele noastre.', 11 | '500' => 'Oops, ceva nu a mers bine pe serverele noastre.', 12 | '503' => 'Ne pare rău, lucrăm la întreținerea site-ului. Te rugăm să încerci mai târziu.', 13 | 14 | ]; 15 | -------------------------------------------------------------------------------- /resources/views/admin/blocks/accordion.blade.php: -------------------------------------------------------------------------------- 1 | @twillBlockTitle('Accordion') 2 | @twillBlockIcon('media-list') 3 | @twillBlockGroup('content') 4 | 5 | @formField('input', [ 6 | 'name' => 'title', 7 | 'label' => __('admin.field.title'), 8 | 'translated' => true, 9 | ]) 10 | 11 | @formField('repeater', [ 12 | 'type' => 'accordionItem' 13 | ]) 14 | -------------------------------------------------------------------------------- /resources/views/admin/blocks/button.blade.php: -------------------------------------------------------------------------------- 1 | @twillBlockTitle('Button') 2 | @twillBlockIcon('colors') 3 | @twillBlockGroup('content') 4 | 5 | @formField('select', [ 6 | 'name' => 'button_color', 7 | 'label' => __('admin.field.buttonColor'), 8 | 'required' => true, 9 | 'default' => 'primary', 10 | 'options' => collect(config('cms.colors'))->map(function($key) { 11 | return [ 12 | 'value' => $key, 13 | 'label' => ucfirst($key), 14 | ]; 15 | })->toArray(), 16 | ]) 17 | 18 | @formField('input', [ 19 | 'name' => 'button_text', 20 | 'label' => __('admin.field.buttonText'), 21 | 'type' => 'text', 22 | 'required' => true, 23 | 'translated' => true, 24 | 'maxlength' => 100, 25 | ]) 26 | 27 | @formField('input', [ 28 | 'name' => 'button_url', 29 | 'label' => __('admin.field.buttonUrl'), 30 | 'type' => 'text', 31 | 'required' => true, 32 | 'translated' => true, 33 | 'maxlength' => 100, 34 | ]) 35 | -------------------------------------------------------------------------------- /resources/views/admin/blocks/callToAction.blade.php: -------------------------------------------------------------------------------- 1 | @twillBlockTitle('Call to action') 2 | @twillBlockIcon('colors') 3 | @twillBlockGroup('content') 4 | 5 | @formField('input', [ 6 | 'name' => 'title', 7 | 'label' => __('admin.field.title'), 8 | 'type' => 'text', 9 | 'required' => true, 10 | 'translated' => true, 11 | 'maxlength' => 100, 12 | ]) 13 | 14 | @include('admin.utils.ckeditor', [ 15 | 'name' => 'description', 16 | 'label' => __('admin.field.description'), 17 | 'translated' => true, 18 | ]) 19 | 20 | @formField('select', [ 21 | 'name' => 'button_color', 22 | 'label' => __('admin.field.buttonColor'), 23 | 'required' => true, 24 | 'default' => 'primary', 25 | 'options' => collect(config('cms.colors'))->map(function($key) { 26 | return [ 27 | 'value' => $key, 28 | 'label' => ucfirst($key), 29 | ]; 30 | })->toArray(), 31 | ]) 32 | 33 | @formField('input', [ 34 | 'name' => 'button_text', 35 | 'label' => __('admin.field.buttonText'), 36 | 'type' => 'text', 37 | 'required' => true, 38 | 'translated' => true, 39 | 'maxlength' => 100, 40 | ]) 41 | 42 | @formField('input', [ 43 | 'name' => 'button_url', 44 | 'label' => __('admin.field.buttonUrl'), 45 | 'type' => 'text', 46 | 'required' => true, 47 | 'translated' => true, 48 | 'maxlength' => 100, 49 | ]) 50 | -------------------------------------------------------------------------------- /resources/views/admin/blocks/cityLabs.blade.php: -------------------------------------------------------------------------------- 1 | @twillBlockTitle('City labs list') 2 | @twillBlockIcon('fix-grid') 3 | @twillBlockGroup('content') 4 | 5 | @formField('browser', [ 6 | 'routePrefix' => 'people', 7 | 'moduleName' => 'cityLabs', 8 | 'name' => 'cityLabs', 9 | 'label' => __('admin.cityLabs'), 10 | 'max' => 100, 11 | ]) 12 | -------------------------------------------------------------------------------- /resources/views/admin/blocks/counter.blade.php: -------------------------------------------------------------------------------- 1 | @twillBlockTitle('Counter') 2 | @twillBlockIcon('star-feature') 3 | @twillBlockGroup('content') 4 | 5 | @formField('input', [ 6 | 'name' => 'title', 7 | 'label' => __('admin.field.title'), 8 | 'translated' => true, 9 | ]) 10 | 11 | @formField('select', [ 12 | 'name' => 'background', 13 | 'label' => __('admin.field.background'), 14 | 'required' => true, 15 | 'default' => 'none', 16 | 'options' => collect(config('cms.colors'))->map(function($key) { 17 | return [ 18 | 'value' => $key, 19 | 'label' => ucfirst($key), 20 | ]; 21 | })->toArray(), 22 | ]) 23 | 24 | @formField('select', [ 25 | 'name' => 'columns', 26 | 'label' => __('admin.field.columns'), 27 | 'required' => true, 28 | 'default' => 3, 29 | 'options' => collect(range(1, 3))->map(function($key) { 30 | return [ 31 | 'value' => $key, 32 | 'label' => $key, 33 | ]; 34 | })->toArray(), 35 | ]) 36 | 37 | @formField('repeater', [ 38 | 'type' => 'counterItem' 39 | ]) 40 | -------------------------------------------------------------------------------- /resources/views/admin/blocks/embed.blade.php: -------------------------------------------------------------------------------- 1 | @twillBlockTitle('Embed') 2 | @twillBlockIcon('revision-single') 3 | @twillBlockGroup('content') 4 | 5 | @formField('input', [ 6 | 'name' => 'title', 7 | 'label' => __('admin.field.title'), 8 | 'translated' => true, 9 | ]) 10 | 11 | @formField('input', [ 12 | 'name' => 'url', 13 | 'label' => __('admin.field.url'), 14 | 'note' => __('admin.fieldNote.embedUrl'), 15 | 'placeholder' => 'https://', 16 | 'required' => true, 17 | ]) 18 | -------------------------------------------------------------------------------- /resources/views/admin/blocks/form.blade.php: -------------------------------------------------------------------------------- 1 | @twillBlockTitle('Form') 2 | @twillBlockIcon('edit') 3 | @twillBlockGroup('content') 4 | 5 | @formField('select', [ 6 | 'name' => 'form', 7 | 'label' => __('admin.form'), 8 | 'required' => true, 9 | 'native' => true, 10 | 'max' => 1, 11 | 'options' => Code4Romania\Cms\Models\Form::query() 12 | ->with('translation:id,form_id,title') 13 | ->get('id') 14 | ->mapWithKeys(fn($form) => [ $form->id => $form->title ]), 15 | ]) 16 | -------------------------------------------------------------------------------- /resources/views/admin/blocks/formSection.blade.php: -------------------------------------------------------------------------------- 1 | @twillBlockTitle('Form section') 2 | @twillBlockIcon('editor') 3 | @twillBlockGroup('form') 4 | 5 | @formField('input', [ 6 | 'name' => 'name', 7 | 'label' => __('admin.field.name'), 8 | 'type' => 'text', 9 | 'required' => true, 10 | 'translated' => true, 11 | ]) 12 | 13 | @include('admin.utils.ckeditor', [ 14 | 'name' => 'description', 15 | 'label' => __('admin.field.description'), 16 | 'translated' => true, 17 | ]) 18 | 19 | @formField('repeater', [ 20 | 'type' => 'formField', 21 | ]) 22 | -------------------------------------------------------------------------------- /resources/views/admin/blocks/html.blade.php: -------------------------------------------------------------------------------- 1 | @twillBlockTitle('HTML') 2 | @twillBlockIcon('website') 3 | @twillBlockGroup('content') 4 | 5 | @formField('input', [ 6 | 'name' => 'html', 7 | 'label' => __('admin.field.html'), 8 | 'type' => 'textarea', 9 | 'rows' => 20, 10 | 'translated' => true, 11 | ]) 12 | -------------------------------------------------------------------------------- /resources/views/admin/blocks/imageGrid.blade.php: -------------------------------------------------------------------------------- 1 | @twillBlockTitle('Image grid') 2 | @twillBlockIcon('fix-grid') 3 | @twillBlockGroup('content') 4 | 5 | @formField('input', [ 6 | 'name' => 'title', 7 | 'label' => __('admin.field.title'), 8 | 'translated' => true, 9 | ]) 10 | 11 | @formField('select', [ 12 | 'name' => 'columns', 13 | 'label' => __('admin.field.columns'), 14 | 'default' => 1, 15 | 'unpack' => true, 16 | 'options' => collect(range(1, 4))->map(function($i) { 17 | return [ 18 | 'value' => $i, 19 | 'label' => $i, 20 | ]; 21 | })->toArray(), 22 | ]) 23 | 24 | @formField('medias', [ 25 | 'name' => 'image', 26 | 'label' => __('admin.field.images'), 27 | 'max' => 12, 28 | 'note' => __('admin.fieldNote.imagesUpTo', ['count' => 12]), 29 | 'required' => true, 30 | 'withVideoUrl' => false, 31 | ]) 32 | -------------------------------------------------------------------------------- /resources/views/admin/blocks/menuItem.blade.php: -------------------------------------------------------------------------------- 1 | @twillBlockTitle('Menu item') 2 | @twillBlockIcon('info') 3 | @twillBlockGroup('menu') 4 | 5 | @include('admin.utils.menu-item-template') 6 | 7 | @formField('repeater', [ 8 | 'type' => 'subMenuItem', 9 | ]) 10 | -------------------------------------------------------------------------------- /resources/views/admin/blocks/newsletter.blade.php: -------------------------------------------------------------------------------- 1 | @twillBlockTitle('Newsletter form') 2 | @twillBlockIcon('info') 3 | @twillBlockGroup('content') 4 | 5 | @inject('mailchimp', 'Code4Romania\Cms\Helpers\MailchimpHelper') 6 | 7 | @include('admin.utils.ckeditor', [ 8 | 'name' => 'text', 9 | 'label' => __('admin.field.text'), 10 | 'translated' => true, 11 | ]) 12 | 13 | @formField('select', [ 14 | 'name' => 'list', 15 | 'label' => __('admin.field.list'), 16 | 'unpack' => false, 17 | 'options' => $mailchimp->getListsCached() 18 | ->map(function($list) { 19 | return [ 20 | 'value' => $list['id'], 21 | 'label' => $list['name'] . " ({$list['id']})", 22 | ]; 23 | }) 24 | ->toArray(), 25 | ]) 26 | -------------------------------------------------------------------------------- /resources/views/admin/blocks/notice.blade.php: -------------------------------------------------------------------------------- 1 | @twillBlockTitle('Notice') 2 | @twillBlockIcon('info') 3 | @twillBlockGroup('content') 4 | 5 | @formField('select', [ 6 | 'name' => 'color', 7 | 'label' => __('admin.field.background'), 8 | 'required' => true, 9 | 'default' => 'none', 10 | 'options' => collect(config('cms.colors')) 11 | ->map(function($key) { 12 | return [ 13 | 'value' => $key, 14 | 'label' => ucfirst($key), 15 | ]; 16 | })->toArray(), 17 | ]) 18 | 19 | @include('admin.utils.ckeditor', [ 20 | 'name' => 'content', 21 | 'label' => __('admin.field.content'), 22 | 'translated' => true, 23 | ]) 24 | -------------------------------------------------------------------------------- /resources/views/admin/blocks/partners.blade.php: -------------------------------------------------------------------------------- 1 | @twillBlockTitle('Partners list') 2 | @twillBlockIcon('fix-grid') 3 | @twillBlockGroup('content') 4 | 5 | @formField('input', [ 6 | 'name' => 'title', 7 | 'label' => __('admin.field.title'), 8 | 'translated' => true, 9 | ]) 10 | 11 | @formField('select', [ 12 | 'name' => 'columns', 13 | 'label' => __('admin.field.columns'), 14 | 'default' => 4, 15 | 'unpack' => true, 16 | 'options' => collect(range(2, 6))->map(function($i) { 17 | return [ 18 | 'value' => $i, 19 | 'label' => $i, 20 | ]; 21 | })->toArray(), 22 | ]) 23 | 24 | @formField('browser', [ 25 | 'moduleName' => 'partners', 26 | 'name' => 'partners', 27 | 'label' => __('admin.partners'), 28 | 'max' => 100, 29 | ]) 30 | -------------------------------------------------------------------------------- /resources/views/admin/blocks/people.blade.php: -------------------------------------------------------------------------------- 1 | @twillBlockTitle('People list') 2 | @twillBlockIcon('fix-grid') 3 | @twillBlockGroup('content') 4 | 5 | @formField('input', [ 6 | 'name' => 'title', 7 | 'label' => __('admin.field.title'), 8 | 'translated' => true, 9 | ]) 10 | 11 | @formField('select', [ 12 | 'name' => 'columns', 13 | 'label' => __('admin.field.columns'), 14 | 'default' => 4, 15 | 'unpack' => true, 16 | 'options' => collect(range(2, 6))->map(function($i) { 17 | return [ 18 | 'value' => $i, 19 | 'label' => $i, 20 | ]; 21 | })->toArray(), 22 | ]) 23 | 24 | @formField('browser', [ 25 | 'routePrefix' => 'people', 26 | 'moduleName' => 'people', 27 | 'name' => 'people', 28 | 'label' => __('admin.people'), 29 | 'max' => 100, 30 | ]) 31 | 32 | @formField('select', [ 33 | 'name' => 'showDescriptions', 34 | 'label' => __('admin.field.showDescriptions'), 35 | 'default' => false, 36 | 'unpack' => true, 37 | 'options' => [ 38 | [ 39 | 'value' => true, 40 | 'label' => 'Yes', 41 | ], 42 | [ 43 | 'value' => false, 44 | 'label' => 'No', 45 | ], 46 | ], 47 | ]) 48 | -------------------------------------------------------------------------------- /resources/views/admin/blocks/quote.blade.php: -------------------------------------------------------------------------------- 1 | @twillBlockTitle('Quote') 2 | @twillBlockIcon('quote') 3 | @twillBlockGroup('content') 4 | 5 | @include('admin.utils.ckeditor', [ 6 | 'name' => 'quote', 7 | 'label' => __('admin.field.quote'), 8 | 'translated' => true, 9 | ]) 10 | 11 | @formField('input', [ 12 | 'name' => 'author', 13 | 'label' => __('admin.field.author'), 14 | 'type' => 'text', 15 | 'required' => true, 16 | 'translated' => true, 17 | 'maxlength' => 100, 18 | ]) 19 | 20 | @formField('input', [ 21 | 'name' => 'affiliation', 22 | 'label' => __('admin.field.affiliation'), 23 | 'type' => 'text', 24 | 'translated' => true, 25 | 'maxlength' => 100, 26 | ]) 27 | -------------------------------------------------------------------------------- /resources/views/admin/blocks/separator.blade.php: -------------------------------------------------------------------------------- 1 | @twillBlockTitle('Separator') 2 | @twillBlockIcon('more-dots') 3 | @twillBlockGroup('content') 4 | -------------------------------------------------------------------------------- /resources/views/admin/blocks/text.blade.php: -------------------------------------------------------------------------------- 1 | @twillBlockTitle('Text') 2 | @twillBlockIcon('text') 3 | @twillBlockGroup('content') 4 | 5 | @include('admin.utils.ckeditor', [ 6 | 'name' => 'text', 7 | 'label' => __('admin.field.text'), 8 | 'translated' => true, 9 | ]) 10 | -------------------------------------------------------------------------------- /resources/views/admin/categories/form.blade.php: -------------------------------------------------------------------------------- 1 | @extends('twill::layouts.form') 2 | 3 | @section('contentFields') 4 | @formField('wysiwyg', [ 5 | 'name' => 'description', 6 | 'label' => __('admin.field.description'), 7 | 'type' => config('cms.editor.type'), 8 | 'toolbarOptions' => config('cms.editor.toolbar'), 9 | 'editSource' => true, 10 | 'translated' => true, 11 | ]) 12 | @endsection 13 | -------------------------------------------------------------------------------- /resources/views/admin/cityLabs/form.blade.php: -------------------------------------------------------------------------------- 1 | @extends('twill::layouts.form', [ 2 | 'disableContentFieldset' => true, 3 | 'additionalFieldsets' => [ 4 | [ 5 | 'fieldset' => 'summary', 6 | 'label' => __('admin.field.summary'), 7 | ], 8 | [ 9 | 'fieldset' => 'content', 10 | 'label' => __('admin.field.content'), 11 | ], 12 | [ 13 | 'fieldset' => 'people', 14 | 'label' => __('admin.people'), 15 | ], 16 | ], 17 | ]) 18 | 19 | @section('fieldsets') 20 | 21 | @formField('medias', [ 22 | 'name' => 'image', 23 | 'label' => __('admin.field.image'), 24 | ]) 25 | 26 | @include('admin.utils.ckeditor', [ 27 | 'name' => 'description', 28 | 'label' => __('admin.field.description'), 29 | 'translated' => true, 30 | ]) 31 | 32 | 33 | 34 | @formField('block_editor', [ 35 | 'withoutSeparator' => true, 36 | 'group' => 'content', 37 | ]) 38 | 39 | 40 | 41 | @formField('browser', [ 42 | 'routePrefix' => 'people', 43 | 'moduleName' => 'people', 44 | 'name' => 'people', 45 | 'label' => false, 46 | 'note' => trans_choice('admin.fieldNote.peopleUpTo', 100), 47 | 'max' => 100, 48 | ]) 49 | 50 | @endsection 51 | -------------------------------------------------------------------------------- /resources/views/admin/menus/form.blade.php: -------------------------------------------------------------------------------- 1 | @extends('twill::layouts.form') 2 | 3 | @section('contentFields') 4 | @formField('select', [ 5 | 'name' => 'location', 6 | 'label' => __('admin.field.menuLocation'), 7 | 'required' => true, 8 | 'default' => false, 9 | 'options' => collect(config('cms.menu.locations'))->map(function($key) { 10 | return [ 11 | 'value' => $key, 12 | 'label' => ucfirst($key), 13 | ]; 14 | })->toArray(), 15 | ]) 16 | @endsection 17 | 18 | @section('fieldsets') 19 | 20 | @formField('block_editor', [ 21 | 'withoutSeparator' => true, 22 | 'group' => 'menu', 23 | ]) 24 | 25 | @endsection 26 | -------------------------------------------------------------------------------- /resources/views/admin/pages/form.blade.php: -------------------------------------------------------------------------------- 1 | @extends('twill::layouts.form', [ 2 | 'contentFieldsetLabel' => __('admin.field.summary'), 3 | 'additionalFieldsets' => [ 4 | [ 5 | 'fieldset' => 'content-blocks', 6 | 'label' => __('admin.field.content'), 7 | ], 8 | ], 9 | ]) 10 | 11 | @section('contentFields') 12 | @formField('checkbox', [ 13 | 'name' => 'show_header', 14 | 'label' => __('admin.field.showHeader'), 15 | 'default' => true, 16 | ]) 17 | 18 | @include('admin.utils.ckeditor', [ 19 | 'name' => 'description', 20 | 'label' => __('admin.field.description'), 21 | 'translated' => true, 22 | ]) 23 | @stop 24 | 25 | 26 | @section('fieldsets') 27 | 28 | @formField('block_editor', [ 29 | 'withoutSeparator' => true, 30 | 'group' => 'content', 31 | ]) 32 | 33 | @stop 34 | -------------------------------------------------------------------------------- /resources/views/admin/partners/form.blade.php: -------------------------------------------------------------------------------- 1 | @extends('twill::layouts.form') 2 | 3 | @section('contentFields') 4 | @formField('input', [ 5 | 'name' => 'website', 6 | 'label' => __('admin.field.website'), 7 | 'type' => 'text', 8 | ]) 9 | 10 | @formField('medias', [ 11 | 'name' => 'logo', 12 | 'label' => __('admin.field.logo'), 13 | 'withVideoUrl' => false, 14 | 'withAddInfo' => false, 15 | 'withCaption' => false, 16 | ]) 17 | @endsection 18 | -------------------------------------------------------------------------------- /resources/views/admin/people/form.blade.php: -------------------------------------------------------------------------------- 1 | @extends('twill::layouts.form', [ 2 | 'translateTitle' => false, 3 | 'controlLanguagesPublication' => true, 4 | 'contentFieldsetLabel' => 'Profile', 5 | 'additionalFieldsets' => [ 6 | [ 7 | 'fieldset' => 'image', 8 | 'label' => __('admin.field.image'), 9 | ], 10 | ], 11 | ]) 12 | 13 | @section('contentFields') 14 | @foreach (['github', 'linkedin'] as $network) 15 | @formField('input', [ 16 | 'name' => $network, 17 | 'label' => config("cms.social.networks.{$network}.name"), 18 | 'translated' => false, 19 | ]) 20 | @endforeach 21 | 22 | @formField('browser', [ 23 | 'routePrefix' => 'people', 24 | 'moduleName' => 'cityLabs', 25 | 'name' => 'cityLab', 26 | 'label' => __('admin.cityLab'), 27 | 'max' => 1, 28 | ]) 29 | 30 | @formField('input', [ 31 | 'name' => 'title', 32 | 'label' => __('admin.field.title'), 33 | 'translated' => true, 34 | ]) 35 | 36 | @include('admin.utils.ckeditor', [ 37 | 'name' => 'description', 38 | 'label' => __('admin.field.description'), 39 | 'translated' => true, 40 | ]) 41 | @endsection 42 | 43 | @section('fieldsets') 44 | 45 | @formField('medias', [ 46 | 'name' => 'image', 47 | 'label' => false, 48 | ]) 49 | 50 | @endsection 51 | -------------------------------------------------------------------------------- /resources/views/admin/posts/form.blade.php: -------------------------------------------------------------------------------- 1 | @extends('twill::layouts.form', [ 2 | 'contentFieldsetLabel' => __('admin.field.summary'), 3 | 'additionalFieldsets' => [ 4 | [ 5 | 'fieldset' => 'content-blocks', 6 | 'label' => __('admin.field.content'), 7 | ], 8 | ], 9 | ]) 10 | 11 | @section('contentFields') 12 | @formField('input', [ 13 | 'name' => 'description', 14 | 'type' => 'textarea', 15 | 'label' => __('admin.field.description'), 16 | 'translated' => true, 17 | ]) 18 | 19 | @formField('medias', [ 20 | 'name' => 'image', 21 | 'label' => __('admin.field.image'), 22 | 'max' => 1, 23 | 'required' => true, 24 | 'withVideoUrl' => false, 25 | 'withAddInfo' => false, 26 | 'withCaption' => false, 27 | ]) 28 | 29 | @formField('input', [ 30 | 'name' => 'author', 31 | 'type' => 'text', 32 | 'label' => __('admin.field.author'), 33 | ]) 34 | 35 | @formField('browser', [ 36 | 'label' => __('admin.categories'), 37 | 'routePrefix' => 'blog', 38 | 'name' => 'categories', 39 | 'moduleName' => 'categories', 40 | 'sortable' => false, 41 | 'max' => 5, 42 | ]) 43 | @endsection 44 | 45 | 46 | @section('fieldsets') 47 | 48 | @formField('block_editor', [ 49 | 'withoutSeparator' => true, 50 | 'group' => 'content', 51 | ]) 52 | 53 | @endsection 54 | -------------------------------------------------------------------------------- /resources/views/admin/repeaters/accordionItem.blade.php: -------------------------------------------------------------------------------- 1 | @twillRepeaterTitle('Accordion Item') 2 | @twillRepeaterTrigger('Add accordion item') 3 | @twillRepeaterGroup('app') 4 | 5 | @formField('input', [ 6 | 'name' => 'header', 7 | 'label' => __('admin.field.header'), 8 | 'translated' => true, 9 | ]) 10 | 11 | @include('admin.utils.ckeditor', [ 12 | 'name' => 'description', 13 | 'label' => __('admin.field.description'), 14 | 'translated' => true, 15 | ]) 16 | -------------------------------------------------------------------------------- /resources/views/admin/repeaters/counterItem.blade.php: -------------------------------------------------------------------------------- 1 | @twillRepeaterTitle('Counter item') 2 | @twillRepeaterTrigger('Add counter item') 3 | @twillRepeaterGroup('app') 4 | 5 | @formField('input', [ 6 | 'name' => 'number', 7 | 'label' => __('admin.field.number'), 8 | 'translated' => false, 9 | ]) 10 | 11 | @formField('input', [ 12 | 'name' => 'label', 13 | 'label' => __('admin.field.label'), 14 | 'translated' => true, 15 | ]) 16 | -------------------------------------------------------------------------------- /resources/views/admin/repeaters/subMenuItem.blade.php: -------------------------------------------------------------------------------- 1 | @twillRepeaterTitle('Submenu item') 2 | @twillRepeaterTrigger('Add submenu item') 3 | @twillRepeaterGroup('app') 4 | 5 | @include('admin.utils.menu-item-template') 6 | -------------------------------------------------------------------------------- /resources/views/admin/responses/form.blade.php: -------------------------------------------------------------------------------- 1 | @extends('twill::layouts.free') 2 | 3 | @section('customPageContent') 4 | 5 | @formField('input', [ 6 | 'readonly' => true, 7 | 'name' => 'created_at', 8 | 'label' => __('form.created_at'), 9 | 'default' => $item->created_at, 10 | ]) 11 | 12 | @formField('input', [ 13 | 'readonly' => true, 14 | 'name' => 'form', 15 | 'label' => __('admin.form'), 16 | 'default' => $item->presentAdmin()->form, 17 | ]) 18 | 19 | 20 | @foreach ($item->data as $sectionIndex => $section) 21 | 22 | @foreach ($section['fields'] as $fieldIndex =>$field) 23 | @formField('input', [ 24 | 'readonly' => true, 25 | 'name' => "field[{$sectionIndex}][{$fieldIndex}]", 26 | 'label' => $field['label'], 27 | 'default' => $field['value'], 28 | ]) 29 | @endforeach 30 | 31 | @endforeach 32 | @endsection 33 | -------------------------------------------------------------------------------- /resources/views/admin/settings/mailchimp.blade.php: -------------------------------------------------------------------------------- 1 | @extends('twill::layouts.settings') 2 | 3 | @section('contentFields') 4 | @formField('input', [ 5 | 'name' => 'apiKey', 6 | 'label' => __('admin.field.apiKey'), 7 | 'translated' => false, 8 | ]) 9 | @stop 10 | -------------------------------------------------------------------------------- /resources/views/admin/settings/social.blade.php: -------------------------------------------------------------------------------- 1 | @extends('twill::layouts.settings') 2 | 3 | @section('contentFields') 4 | @foreach (config('cms.social.networks') as $id => $network) 5 | @formField('input', [ 6 | 'name' => $id, 7 | 'label' => $network['name'], 8 | 'prefix' => $network['baseUrl'], 9 | 'translated' => false, 10 | ]) 11 | @endforeach 12 | @stop 13 | -------------------------------------------------------------------------------- /resources/views/components/button.blade.php: -------------------------------------------------------------------------------- 1 | @inject('url', 'Code4Romania\Cms\Helpers\UrlHelper') 2 | 3 | @php 4 | $href ??= false; 5 | $type ??= 'button'; 6 | $color ??= 'primary'; 7 | 8 | $buttonBase = 'relative inline-block w-full px-6 py-2 font-semibold leading-snug tracking-wide text-center shadow-md sm:w-auto hover:shadow-lg focus:outline-none focus:shadow-md'; 9 | 10 | switch ($color) { 11 | case 'primary': 12 | $buttonColor = 'text-white bg-primary-500 focus:bg-primary-600'; 13 | break; 14 | 15 | case 'warning': 16 | $buttonColor = 'text-black bg-warning-500 focus:bg-warning-600'; 17 | break; 18 | 19 | case 'danger': 20 | $buttonColor = 'text-white bg-danger-500 focus:bg-danger-600'; 21 | break; 22 | 23 | case 'success': 24 | $buttonColor = 'text-white bg-success-500 focus:bg-success-600'; 25 | break; 26 | 27 | case 'black': 28 | $buttonColor = 'text-white bg-black focus:bg-gray-900'; 29 | break; 30 | 31 | case 'gray': 32 | $buttonColor = 'text-white bg-gray-500 focus:bg-gray-600'; 33 | break; 34 | 35 | default: 36 | $buttonColor = 'border focus:bg-gray-100'; 37 | break; 38 | } 39 | @endphp 40 | 41 | @if ($href !== false) 42 | isExternal($href)) 47 | rel="noopener noreferrer" 48 | target="_blank" 49 | @endif 50 | >{{ $slot }} 51 | @else 52 | 56 | @endif 57 | -------------------------------------------------------------------------------- /resources/views/components/container.blade.php: -------------------------------------------------------------------------------- 1 |
merge(['class' => 'grid row-gap-12 lg:row-gap-20']) }}> 2 | {{ $slot }} 3 |
4 | -------------------------------------------------------------------------------- /resources/views/components/figure.blade.php: -------------------------------------------------------------------------------- 1 | @php 2 | $alt ??= false; 3 | $caption ??= false; 4 | $class ??= ''; 5 | $lqip ??= false; 6 | @endphp 7 | 8 |
9 | 10 | 11 | @if ($caption) 12 |
13 |
14 | {{ svg('icons/camera-lens', 'w-6 h-6 text-gray-400') }} 15 |
16 |

{{ $caption }}

17 |
18 | @endif 19 |
20 | -------------------------------------------------------------------------------- /resources/views/components/hex-badge.blade.php: -------------------------------------------------------------------------------- 1 | @php 2 | $title ??= ''; 3 | $icon ??= ''; 4 | @endphp 5 | 6 |
7 |
8 | {{ svg('hex', 'block w-auto h-16 md:h-24 text-primary-500') }} 9 | {{ svg("icons/$icon", 'absolute inset-0 p-4 md:p-6 m-auto text-white') }} 10 |
11 |

{{ $title }}

12 |
13 | -------------------------------------------------------------------------------- /resources/views/components/image.blade.php: -------------------------------------------------------------------------------- 1 | @php 2 | $alt = $alt ?? ''; 3 | $class = $class ?? 'block w-full'; 4 | $lqip = $lqip ?? false; 5 | @endphp 6 | 7 | {{ $alt }} 19 | -------------------------------------------------------------------------------- /resources/views/components/input.blade.php: -------------------------------------------------------------------------------- 1 | @php 2 | $type = $type ?? 'text'; 3 | $required = $required ?? false; 4 | $label = $label ?? null; 5 | $placeholder = $placeholder ?? null; 6 | @endphp 7 | 8 |
9 | @if ($label) 10 | 11 | @endif 12 | 20 |
21 | -------------------------------------------------------------------------------- /resources/views/components/link.blade.php: -------------------------------------------------------------------------------- 1 | {{ $slot }} 9 | -------------------------------------------------------------------------------- /resources/views/components/notice.blade.php: -------------------------------------------------------------------------------- 1 | @php 2 | $classes = collect(); 3 | 4 | switch ($color) { 5 | case 'primary': 6 | $classes->push('bg-primary-700'); 7 | $classes->push('text-white'); 8 | break; 9 | 10 | case 'warning': 11 | $classes->push('bg-warning-400'); 12 | $classes->push('text-black'); 13 | break; 14 | 15 | case 'danger': 16 | $classes->push('bg-danger-700'); 17 | $classes->push('text-white'); 18 | break; 19 | 20 | case 'gray': 21 | $classes->push('bg-gray-800'); 22 | $classes->push('text-white'); 23 | break; 24 | } 25 | @endphp 26 | 27 |
28 |
29 | {!! $content !!} 30 |
31 |
32 | -------------------------------------------------------------------------------- /resources/views/components/social-network-link.blade.php: -------------------------------------------------------------------------------- 1 | @php 2 | $network ??= null; 3 | $link ??= null; 4 | @endphp 5 | 6 | @if ($link) 7 | 13 | {{ svg("icons/$network", 'block h-4 fill-current') }} 14 | 15 | @endif 16 | -------------------------------------------------------------------------------- /resources/views/emails/formResponseSubmitted.blade.php: -------------------------------------------------------------------------------- 1 | @component('mail::message') 2 | 3 | {{-- Begin section --}} 4 | @foreach ($response->data as $section) 5 | 6 | # {{ $section['title'] }} 7 | 8 | {{-- Begin field --}} 9 | @foreach ($section['fields'] as $field) 10 |

11 | {{ $loop->iteration }}. {{ $field['label'] }}
12 | {{ $field['value'] ?? '-' }} 13 |

14 | @endforeach 15 | {{-- End field --}} 16 | 17 | @endforeach 18 | {{-- End section --}} 19 | 20 | @endcomponent 21 | -------------------------------------------------------------------------------- /resources/views/errors/404.blade.php: -------------------------------------------------------------------------------- 1 | @extends('front.layouts.app') 2 | 3 | @section('content') 4 |
5 |

{{ __('errors.404') }}

6 | 7 |
8 | {{ __('errors.back') }} 12 |
13 | 14 |
15 | {{ svg('404', 'block w-full h-full') }} 16 |
17 |
18 | @endsection 19 | -------------------------------------------------------------------------------- /resources/views/front/blocks/_container.blade.php: -------------------------------------------------------------------------------- 1 | 2 | {!! $item->renderBlocks(false) !!} 3 | 4 | -------------------------------------------------------------------------------- /resources/views/front/blocks/accordion.blade.php: -------------------------------------------------------------------------------- 1 | @inject('url', 'Code4Romania\Cms\Helpers\UrlHelper') 2 | 3 |
4 | @if ($block->translatedInput('title')) 5 |

{{ $block->translatedInput('title') }}

6 | @endif 7 | 8 | @foreach ($block->children as $child) 9 | @include('front.blocks.accordionItem', [ 10 | 'index' => $loop->index, 11 | 'block' => $child, 12 | 'isPreview' => $url->isAdminUrl(), 13 | ]) 14 | @endforeach 15 |
16 | -------------------------------------------------------------------------------- /resources/views/front/blocks/accordionItem.blade.php: -------------------------------------------------------------------------------- 1 | @php 2 | $isPreview = $isPreview ?? false; 3 | @endphp 4 | 5 |
6 |

16 | {{ $block->translatedInput('header') }} 17 | 18 | 19 | 20 |

21 |
{!! $block->translatedInput('description') !!}
25 |
26 | -------------------------------------------------------------------------------- /resources/views/front/blocks/button.blade.php: -------------------------------------------------------------------------------- 1 |
2 | {{ $block->translatedInput('button_text') }} 6 |
7 | -------------------------------------------------------------------------------- /resources/views/front/blocks/callToAction.blade.php: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

{{ $block->translatedInput('title') }}

5 |
6 | {!! $block->translatedInput('description') !!} 7 |
8 |
9 | 10 |
11 | {{ $block->translatedInput('button_text') }} 15 |
16 |
17 |
18 | -------------------------------------------------------------------------------- /resources/views/front/blocks/cityLabs.blade.php: -------------------------------------------------------------------------------- 1 |
2 | @foreach ($block->present()->cityLabsListPublished as $cityLab) 3 | 9 | 10 |
11 | {{ $cityLab->name }} 12 |
13 | @endforeach 14 |
15 | -------------------------------------------------------------------------------- /resources/views/front/blocks/counterItem.blade.php: -------------------------------------------------------------------------------- 1 |
2 |
3 | {{ svg('hex', 'absolute inset-0 z-0 ' . $badgeBackground) }} 4 | {{ $block->input('number') }} 5 |
6 | 7 |
{{ $block->translatedInput('label') }}
8 |
9 | -------------------------------------------------------------------------------- /resources/views/front/blocks/embed.blade.php: -------------------------------------------------------------------------------- 1 | @php 2 | $ratio = $block->present()->embedAspectRatio; 3 | @endphp 4 | 5 |
6 | @if ($block->translatedInput('title')) 7 |

{{ $block->translatedInput('title') }}

8 | @endif 9 | 10 |
11 | {!! $block->present()->embedCode !!} 12 |
13 |
14 | -------------------------------------------------------------------------------- /resources/views/front/blocks/form.blade.php: -------------------------------------------------------------------------------- 1 | @php 2 | $form = $block->present()->formPublished; 3 | 4 | if (is_null($form)) { 5 | return; 6 | } 7 | 8 | $sections = $form->blocks->where('type', 'formSection'); 9 | 10 | $fieldsBySection = $form->blocks 11 | ->where('type', 'formField') 12 | ->filter(fn ($block) => $block->parent_id !== null) 13 | ->groupBy('parent_id'); 14 | 15 | $data = $sections->map(function($section, $sectionIndex) use ($fieldsBySection) { 16 | return $fieldsBySection 17 | ->get($section->id) 18 | ->map(fn ($field, $fieldIndex) => null); 19 | }); 20 | @endphp 21 | 22 |
23 | @foreach ($sections as $block) 24 | @include('front.blocks.formSection', [ 25 | 'block' => $block, 26 | 'fields' => $fieldsBySection->get($block->id), 27 | 'sectionIndex' => $loop->index, 28 | ]) 29 | @endforeach 30 | 31 |
32 | @csrf 33 | 34 | {{ __('form.submit') }} 35 |
36 |
37 | -------------------------------------------------------------------------------- /resources/views/front/blocks/formSection.blade.php: -------------------------------------------------------------------------------- 1 | @php 2 | $fields ??= $block->children; 3 | $sectionIndex ??= null; 4 | @endphp 5 | 6 |
7 | 8 |

{{ $block->translatedInput('name') }}

9 |
{!! $block->translatedInput('description') !!}
10 |
11 | 12 |
13 | @foreach ($fields as $field) 14 | @include('front.form._field', [ 15 | 'type' => $field->input('type'), 16 | 'field' => $field, 17 | 'fieldIndex' => $loop->index, 18 | ]) 19 | @endforeach 20 |
21 |
22 | -------------------------------------------------------------------------------- /resources/views/front/blocks/hero.blade.php: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 |

{{ $block->translatedInput('title') }}

5 |

{{ $block->translatedInput('subtitle') }}

6 |
7 | 8 |
9 | {!! $block->translatedInput('content') !!} 10 |
11 | 12 | {{ $block->translatedInput('button_text') }} 16 | 17 |
18 | @if ($block->hasImage('image')) 19 |
20 | 27 |
28 | @endif 29 |
30 | -------------------------------------------------------------------------------- /resources/views/front/blocks/html.blade.php: -------------------------------------------------------------------------------- 1 |
2 | {!! $block->translatedInput('html') !!} 3 |
4 | -------------------------------------------------------------------------------- /resources/views/front/blocks/imageGrid.blade.php: -------------------------------------------------------------------------------- 1 | @php 2 | switch ((int) $block->input('columns')) { 3 | case 1: 4 | default: 5 | $cols = 'md:grid-cols-1'; 6 | break; 7 | 8 | case 2: 9 | $cols = 'md:grid-cols-2'; 10 | break; 11 | 12 | case 3: 13 | $cols = 'md:grid-cols-3'; 14 | break; 15 | 16 | case 4: 17 | $cols = 'md:grid-cols-4'; 18 | break; 19 | } 20 | @endphp 21 | 22 |
23 | @if ($block->translatedInput('title')) 24 |

{{ $block->translatedInput('title') }}

25 | @endif 26 | 27 |
28 | @foreach ($block->imageObjects('image') as $media) 29 | 34 | @endforeach 35 |
36 |
37 | -------------------------------------------------------------------------------- /resources/views/front/blocks/imageText.blade.php: -------------------------------------------------------------------------------- 1 | @php 2 | $container = collect(); 3 | $imageRight = collect('col-span-1'); 4 | $content = collect('ck-content'); 5 | 6 | switch ($block->input('width')) { 7 | case '1/4': 8 | $container->push('md:grid-cols-4'); 9 | $imageRight->push('md:col-start-4'); 10 | $content->push('md:col-span-3'); 11 | break; 12 | 13 | case '1/3': 14 | $container->push('md:grid-cols-3'); 15 | $imageRight->push('md:col-start-3'); 16 | $content->push('md:col-span-2'); 17 | break; 18 | 19 | case '1/2': 20 | $container->push('md:grid-cols-2'); 21 | $imageRight->push('md:col-start-2'); 22 | $content->push('md:col-span-1'); 23 | break; 24 | } 25 | 26 | switch ($block->input('valign')) { 27 | case 'top': 28 | $container->push('items-start'); 29 | break; 30 | 31 | case 'center': 32 | $container->push('items-center'); 33 | break; 34 | 35 | case 'bottom': 36 | $container->push('items-end'); 37 | break; 38 | } 39 | 40 | if ($block->input('position') === 'right') { 41 | $figure = $imageRight->implode(' '); 42 | } else { 43 | $figure = null; 44 | } 45 | @endphp 46 | 47 |
48 | 55 | 56 |
57 | {!! $block->translatedInput('text') !!} 58 |
59 |
60 | 61 | -------------------------------------------------------------------------------- /resources/views/front/blocks/newsletter.blade.php: -------------------------------------------------------------------------------- 1 |
2 |
3 | {!! $block->translatedInput('text') !!} 4 |
5 | 6 |
7 | @csrf 8 | 9 | 10 | 11 | 25 | 26 | {{ __('form.subscribe') }} 27 | 28 | @if (session('success')) 29 | 32 | @endif 33 |
34 |
35 | -------------------------------------------------------------------------------- /resources/views/front/blocks/notice.blade.php: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /resources/views/front/blocks/partners.blade.php: -------------------------------------------------------------------------------- 1 | @php 2 | switch ($block->input('columns')) { 3 | case 2: 4 | $columns = 'sm:grid-cols-2'; 5 | break; 6 | 7 | case 3: 8 | $columns = 'sm:grid-cols-2 lg:grid-cols-3'; 9 | break; 10 | 11 | case 4: 12 | $columns = 'sm:grid-cols-2 lg:grid-cols-4'; 13 | break; 14 | 15 | case 5: 16 | $columns = 'sm:grid-cols-2 lg:grid-cols-5'; 17 | break; 18 | 19 | case 6: 20 | $columns = 'sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-6'; 21 | break; 22 | 23 | default: 24 | $columns = null; 25 | break; 26 | } 27 | @endphp 28 | 29 |
30 | @if ($block->translatedInput('title')) 31 |

{{ $block->translatedInput('title') }}

32 | @endif 33 | 34 |
35 | @foreach ($block->present()->partnersListPublished as $partner) 36 | @include('front.partners.card', [ 37 | 'partner' => $partner, 38 | ]) 39 | @endforeach 40 |
41 |
42 | -------------------------------------------------------------------------------- /resources/views/front/blocks/people.blade.php: -------------------------------------------------------------------------------- 1 | @php 2 | switch ($block->input('columns')) { 3 | case 2: 4 | $columns = 'sm:grid-cols-2'; 5 | break; 6 | 7 | case 3: 8 | $columns = 'sm:grid-cols-2 lg:grid-cols-3'; 9 | break; 10 | 11 | case 4: 12 | $columns = 'sm:grid-cols-2 lg:grid-cols-4'; 13 | break; 14 | 15 | case 5: 16 | $columns = 'sm:grid-cols-2 lg:grid-cols-5'; 17 | break; 18 | 19 | case 6: 20 | $columns = 'sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-6'; 21 | break; 22 | 23 | default: 24 | $columns = null; 25 | break; 26 | } 27 | @endphp 28 | 29 |
30 | @if ($block->translatedInput('title')) 31 |

{{ $block->translatedInput('title') }}

32 | @endif 33 | 34 |
35 | @foreach ($block->present()->peopleListPublished as $person) 36 | @include('front.people.card', [ 37 | 'person' => $person, 38 | 'showDescription' => $block->input('showDescriptions'), 39 | ]) 40 | @endforeach 41 |
42 |
43 | -------------------------------------------------------------------------------- /resources/views/front/blocks/quote.blade.php: -------------------------------------------------------------------------------- 1 |
2 |
3 | {!! $block->translatedInput('quote') !!} 4 |
5 | 6 | @if ($block->translatedInput('author')) 7 | 8 | {{ $block->translatedInput('author') }} 9 | 10 | @if ($block->translatedInput('affiliation')) 11 |
{{ $block->translatedInput('affiliation') }} 12 | @endif 13 |
14 | @endif 15 |
16 | -------------------------------------------------------------------------------- /resources/views/front/blocks/separator.blade.php: -------------------------------------------------------------------------------- 1 |
2 |
3 |
4 | -------------------------------------------------------------------------------- /resources/views/front/blocks/text.blade.php: -------------------------------------------------------------------------------- 1 |
2 | {!! $block->translatedInput('text') !!} 3 |
4 | -------------------------------------------------------------------------------- /resources/views/front/categories/show.blade.php: -------------------------------------------------------------------------------- 1 | @inject('settings', 'Code4Romania\Cms\Helpers\settingsHelper') 2 | 3 | @extends('front.layouts.app') 4 | 5 | @section('content') 6 | @include('front.partials.page-header', [ 7 | 'title' => $item->title, 8 | 'description' => $item->description, 9 | ]) 10 | 11 | 12 | @foreach ($item->posts as $item) 13 | @include('front.posts.card', [ 14 | 'item' => $item, 15 | ]) 16 | @endforeach 17 | 18 | @endsection 19 | -------------------------------------------------------------------------------- /resources/views/front/cityLabs/index.blade.php: -------------------------------------------------------------------------------- 1 | @extends('front.layouts.app') 2 | 3 | @section('content') 4 | 5 | @foreach ($items as $item) 6 | 7 | {{ $item->name }} 8 | @endforeach 9 | @endsection 10 | -------------------------------------------------------------------------------- /resources/views/front/cityLabs/show.blade.php: -------------------------------------------------------------------------------- 1 | @extends('front.layouts.app') 2 | 3 | @section('content') 4 | 5 |
6 |
7 | 16 |
17 |

{{ $item->name }}

18 | {!! $item->description !!} 19 |
20 |
21 |
22 | 23 |
24 | 25 | 26 |
27 | @foreach ($item->people as $person) 28 | @include('front.people.card', [ 29 | 'person' => $person, 30 | ]) 31 | @endforeach 32 |
33 |
34 | 35 | {!! $item->renderBlocks(false) !!} 36 |
37 | @endsection 38 | -------------------------------------------------------------------------------- /resources/views/front/form/_error.blade.php: -------------------------------------------------------------------------------- 1 | @if ($errors->has($name)) 2 | 5 | @endif 6 | -------------------------------------------------------------------------------- /resources/views/front/form/_field.blade.php: -------------------------------------------------------------------------------- 1 | @php 2 | $sectionIndex ??= null; 3 | $fieldIndex ??= null; 4 | 5 | $fieldName = "fields[{$sectionIndex}][{$fieldIndex}]"; 6 | $fieldId = "fields.{$sectionIndex}.{$fieldIndex}"; 7 | 8 | $fieldClass = collect(['field', 'block']); 9 | 10 | switch ($field->input('width')) { 11 | case 'half': 12 | $fieldClass->push('col-span-1'); 13 | break; 14 | 15 | case 'full': 16 | default: 17 | $fieldClass->push('md:col-span-2'); 18 | break; 19 | } 20 | @endphp 21 | 22 |
23 | 30 | 31 | @includeFirst(["front.form.{$type}", 'front.form.input'], [ 32 | 'type' => $type, 33 | 'name' => $fieldName, 34 | 'id' => $fieldId, 35 | 'field' => $field, 36 | 'attributes' => $field->present()->formFieldAttributes, 37 | ]) 38 | 39 | @include('front.form._error', [ 40 | 'name' => $fieldId, 41 | ]) 42 |
43 | -------------------------------------------------------------------------------- /resources/views/front/form/checkbox.blade.php: -------------------------------------------------------------------------------- 1 |
2 | @foreach ($field->present()->formFieldOptions as $option) 3 | 7 | @endforeach 8 |
9 | -------------------------------------------------------------------------------- /resources/views/front/form/input.blade.php: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /resources/views/front/form/radio.blade.php: -------------------------------------------------------------------------------- 1 |
2 | @foreach ($field->present()->formFieldOptions as $option) 3 | 7 | @endforeach 8 |
9 | -------------------------------------------------------------------------------- /resources/views/front/form/select.blade.php: -------------------------------------------------------------------------------- 1 | 6 | -------------------------------------------------------------------------------- /resources/views/front/form/textarea.blade.php: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /resources/views/front/layouts/app.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | {!! \Artesaos\SEOTools\Facades\SEOTools::generate() !!} 10 | 11 | 12 | 13 | {{-- Favicons --}} 14 | 15 | 16 | 17 | 18 | 19 | 20 | @include('front.partials.header') 21 | 22 |
23 | @yield('content') 24 |
25 | 26 | @include('front.partials.footer') 27 | @include('front.partials.scripts') 28 | @include('front.partials.analytics') 29 | 30 | 31 | -------------------------------------------------------------------------------- /resources/views/front/layouts/blocks.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | @yield('content') 14 |
15 | 16 | @include('front.partials.scripts') 17 | 18 | 19 | -------------------------------------------------------------------------------- /resources/views/front/menu/footer.blade.php: -------------------------------------------------------------------------------- 1 |
2 | @foreach (Code4Romania\Cms\Models\Menu::getLocation('footer') as $item) 3 | 26 | @endforeach 27 |
28 | -------------------------------------------------------------------------------- /resources/views/front/pages/show.blade.php: -------------------------------------------------------------------------------- 1 | @extends('front.layouts.app') 2 | 3 | @section('content') 4 | @includeWhen($item->show_header === true, 'front.partials.page-header', [ 5 | 'title' => $item->title, 6 | 'description' => $item->description, 7 | ]) 8 | 9 | @include('front.blocks._container', [ 10 | 'item' => $item 11 | ]) 12 | @endsection 13 | -------------------------------------------------------------------------------- /resources/views/front/partials/analytics.blade.php: -------------------------------------------------------------------------------- 1 | @if (config('analytics.tracking_id')) 2 | 29 | @endif 30 | -------------------------------------------------------------------------------- /resources/views/front/partials/footer.blade.php: -------------------------------------------------------------------------------- 1 | @inject('social', 'Code4Romania\Cms\Helpers\SocialHelper') 2 | 3 | 29 | -------------------------------------------------------------------------------- /resources/views/front/partials/header.blade.php: -------------------------------------------------------------------------------- 1 | 20 | 21 | @include('front.partials.notice') 22 | -------------------------------------------------------------------------------- /resources/views/front/partials/notice.blade.php: -------------------------------------------------------------------------------- 1 | @inject('settings', 'Code4Romania\Cms\Helpers\SettingsHelper') 2 | 3 | @php 4 | $site = $settings->getSection('site'); 5 | $enabled = boolval($site['notice_enabled'] ?? false); 6 | $content = $site['notice_content'] ?? null; 7 | 8 | // workaround until this get merged: https://github.com/area17/twill/pull/653 9 | if (isset($site['notice_color'])) { 10 | $color = config('cms.colors')[$site['notice_color']] ?? null; 11 | } 12 | 13 | $color ??= null; 14 | @endphp 15 | 16 | @if ($enabled) 17 | 18 | @endif 19 | -------------------------------------------------------------------------------- /resources/views/front/partials/page-header.blade.php: -------------------------------------------------------------------------------- 1 | @php 2 | $title ??= ''; 3 | $description ??= ''; 4 | @endphp 5 | 6 |
7 |

8 | {{ $title }} 9 |

10 | 11 | @if ($description) 12 |
{!! $description !!}
13 | @endif 14 |
15 | -------------------------------------------------------------------------------- /resources/views/front/partials/scripts.blade.php: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /resources/views/front/partners/card.blade.php: -------------------------------------------------------------------------------- 1 | @php 2 | $outerClass = 'relative h-0 bg-white border border-gray-400 aspect-ratio-1/1'; 3 | @endphp 4 | 5 | 33 | -------------------------------------------------------------------------------- /resources/views/front/people/card.blade.php: -------------------------------------------------------------------------------- 1 | @php 2 | $showDescription ??= false; 3 | @endphp 4 | 5 |
6 |
7 |
8 | 14 | 15 | @foreach (['github', 'linkedin'] as $network) 16 | @continue(!$person->$network) 17 | 18 | 19 | @endforeach 20 |
21 | 22 |
23 |

{{ $person->name }}

24 | {{ $person->title }} 25 |
26 | 27 |
28 | 29 | @if ($showDescription && $person->description) 30 |
31 | {!! $person->description !!} 32 |
33 | @endif 34 |
35 | -------------------------------------------------------------------------------- /resources/views/front/posts/card.blade.php: -------------------------------------------------------------------------------- 1 |
2 | 3 |
4 | @if ($item->hasImage('image')) 5 | 11 | @else 12 | {{ svg('logo-sm-gray', 'absolute inset-0 flex items-center justify-center w-full h-full p-10 lg:p-20') }} 13 | @endif 14 |
15 |
16 |
17 | {{ $item->present()->publishDate }} 18 | 19 | @if ($item->author) 20 | 21 | {{ $item->author }} 22 | @endif 23 |
24 | 25 |

{{ $item->title }}

26 | 27 |

{{ $item->description }}

28 | 29 | @if ($item->categories->count()) 30 |
31 | {{ $item->categories->pluck('title')->join(', ') }} 32 |
33 | @endif 34 |
35 |
36 |
37 | -------------------------------------------------------------------------------- /resources/views/front/posts/index.blade.php: -------------------------------------------------------------------------------- 1 | @inject('settings', 'Code4Romania\Cms\Helpers\settingsHelper') 2 | 3 | @extends('front.layouts.app') 4 | 5 | @section('content') 6 | @include('front.partials.page-header', [ 7 | 'title' => $settings->get('blogTitle', 'site'), 8 | 'description' => $settings->get('blogDescription', 'site'), 9 | ]) 10 | 11 | 12 | @foreach ($items as $item) 13 | @include('front.posts.card', [ 14 | 'item' => $item, 15 | ]) 16 | @endforeach 17 | 18 | {{ $items->links('front.partials.pagination') }} 19 | 20 | @endsection 21 | -------------------------------------------------------------------------------- /routes/admin.php: -------------------------------------------------------------------------------- 1 | group(static function (): void { 11 | Route::module('posts'); 12 | Route::module('categories'); 13 | }); 14 | 15 | if (config('cms.enabled.people')) { 16 | Route::prefix('people')->group(static function (): void { 17 | Route::module('people'); 18 | Route::module('cityLabs'); 19 | }); 20 | } 21 | 22 | Route::prefix('forms')->group(static function (): void { 23 | Route::module('forms'); 24 | Route::module('responses'); 25 | }); 26 | } 27 | -------------------------------------------------------------------------------- /src/Commands/Build.php: -------------------------------------------------------------------------------- 1 | option('install')) { 29 | $this->info('Installing npm dependencies'); 30 | $this->runProcess(['npm', 'install', '--no-save', '--prefer-offline', '--no-audit']); 31 | $this->runProcess(['npm', 'ci'], base_path('vendor/area17/twill')); 32 | } else { 33 | $this->info('Reusing npm dependencies'); 34 | } 35 | 36 | $this->info('Building the ckeditor'); 37 | $this->runProcess(['npm', 'run', 'ckeditor:build']); 38 | 39 | $this->info('Building the public frontend'); 40 | $this->runProcess(['npm', 'run', 'prod']); 41 | 42 | $this->info('Building the Twill frontend'); 43 | $this->call('twill:build', ['--noInstall' => true]); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/Commands/Command.php: -------------------------------------------------------------------------------- 1 | setTty(Process::isTtySupported()) 16 | ->setWorkingDirectory($cwd ?? getcwd()) 17 | ->setTimeout($timeout) 18 | ->mustRun(); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/FakerServiceProvider.php: -------------------------------------------------------------------------------- 1 | app->bind(Generator::class, static function () { 23 | $faker = Factory::create(); 24 | $faker->addProvider(new PicsumImageProvider($faker)); 25 | 26 | return $faker; 27 | }); 28 | } 29 | 30 | /** 31 | * Bootstrap services. 32 | * 33 | * @return void 34 | */ 35 | public function boot() 36 | { 37 | // 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/Helpers/MailchimpHelper.php: -------------------------------------------------------------------------------- 1 | map(function ($list) { 20 | return [ 21 | 'id' => $list['id'], 22 | 'name' => $list['name'], 23 | ]; 24 | }); 25 | }); 26 | } catch (Throwable $th) { 27 | return collect(); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Helpers/SettingsHelper.php: -------------------------------------------------------------------------------- 1 | saveAll($fields, $section); 22 | } 23 | 24 | public static function getSection(string $section): array 25 | { 26 | return Cache::rememberForever('settings.' . $section, function () use ($section) { 27 | return Setting::query() 28 | ->where('section', $section) 29 | ->with('translations') 30 | ->get() 31 | ->mapWithKeys(fn ($item) => [$item->key => $item->value]) 32 | ->toArray(); 33 | }); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Helpers/SocialHelper.php: -------------------------------------------------------------------------------- 1 | map(function ($value, $key) use ($settings): ?string { 20 | $username = $settings[$key] ?? null; 21 | 22 | if ($username === null) { 23 | return null; 24 | } 25 | 26 | return $value['baseUrl'] . $username; 27 | }) 28 | ->filter(); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Http/Controllers/Admin/CategoryController.php: -------------------------------------------------------------------------------- 1 | */ 16 | protected $indexOptions = [ 17 | 'editInModal' => false, 18 | 'publish' => false, 19 | ]; 20 | 21 | /** @var array */ 22 | protected $indexWith = [ 23 | 'translations', 24 | ]; 25 | 26 | /** @var array */ 27 | protected $indexForm = [ 28 | 'translations', 29 | ]; 30 | } 31 | -------------------------------------------------------------------------------- /src/Http/Controllers/Admin/CityLabController.php: -------------------------------------------------------------------------------- 1 | */ 28 | protected $indexWith = [ 29 | 'medias', 30 | ]; 31 | 32 | /** @var array */ 33 | protected $indexOptions = []; 34 | 35 | /** @var array */ 36 | protected $indexColumns = [ 37 | 'image' => [ 38 | 'thumb' => true, 39 | 'field' => 'image', 40 | 'variant' => [ 41 | 'role' => 'image', 42 | 'crop' => 'default', 43 | ], 44 | ], 45 | 'name' => [ 46 | 'title' => 'Name', 47 | 'field' => 'name', 48 | 'sort' => true, 49 | ], 50 | 'people' => [ 51 | 'title' => 'People', 52 | 'field' => 'people_count', 53 | 'sort' => true, 54 | ], 55 | ]; 56 | 57 | /** @var array */ 58 | protected $browserColumns = [ 59 | 'title' => [ 60 | 'title' => 'Name', 61 | 'field' => 'name', 62 | ], 63 | ]; 64 | } 65 | -------------------------------------------------------------------------------- /src/Http/Controllers/Admin/FormController.php: -------------------------------------------------------------------------------- 1 | */ 12 | protected $formWith = [ 13 | 'blocks.files', 14 | 'blocks.medias', 15 | ]; 16 | 17 | /** @var array */ 18 | protected $indexOptions = [ 19 | 'permalink' => false, 20 | 'editInModal' => false, 21 | 'reorder' => false, 22 | ]; 23 | } 24 | -------------------------------------------------------------------------------- /src/Http/Controllers/Admin/MenuController.php: -------------------------------------------------------------------------------- 1 | */ 12 | protected $formWith = [ 13 | 'blocks.files', 14 | 'blocks.medias', 15 | ]; 16 | 17 | /** @var array */ 18 | protected $indexOptions = [ 19 | 'permalink' => false, 20 | 'editInModal' => false, 21 | 'reorder' => false, 22 | ]; 23 | 24 | /** 25 | * Disable the content editor (full screen block editor). 26 | * 27 | * @var bool 28 | */ 29 | protected $disableEditor = true; 30 | 31 | /** @var array */ 32 | protected $indexColumns = [ 33 | 'title' => [ 34 | 'title' => 'Title', 35 | 'field' => 'title', 36 | 'sort' => true, 37 | ], 38 | 'location' => [ 39 | 'title' => 'Location', 40 | 'field' => 'location', 41 | ], 42 | ]; 43 | } 44 | -------------------------------------------------------------------------------- /src/Http/Controllers/Admin/ModuleController.php: -------------------------------------------------------------------------------- 1 | moduleHas('translations') ? '{language}/' : '') 26 | . ($this->moduleHas('revisions') ? '{preview}/' : '') 27 | . ($this->permalinkBase ?? $this->moduleName) 28 | . (isset($this->permalinkBase) && empty($this->permalinkBase) ? '' : '/'); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Http/Controllers/Admin/PageController.php: -------------------------------------------------------------------------------- 1 | */ 19 | protected $indexWith = [ 20 | 'translations', 21 | ]; 22 | } 23 | -------------------------------------------------------------------------------- /src/Http/Controllers/Admin/PartnerController.php: -------------------------------------------------------------------------------- 1 | */ 24 | protected $indexWith = [ 25 | 'medias', 26 | ]; 27 | 28 | /** @var array */ 29 | protected $indexOptions = [ 30 | 'permalink' => false, 31 | 'reorder' => true, 32 | ]; 33 | 34 | /** @var array */ 35 | protected $indexColumns = [ 36 | 'image' => [ 37 | 'thumb' => true, 38 | 'field' => 'image', 39 | 'variant' => [ 40 | 'role' => 'logo', 41 | 'crop' => 'default', 42 | ], 43 | ], 44 | 'name' => [ 45 | 'title' => 'Name', 46 | 'field' => 'name', 47 | 'sort' => true, 48 | ], 49 | 'website' => [ 50 | 'title' => 'Website', 51 | 'field' => 'website', 52 | ], 53 | ]; 54 | } 55 | -------------------------------------------------------------------------------- /src/Http/Controllers/Admin/PostController.php: -------------------------------------------------------------------------------- 1 | */ 19 | protected $indexWith = [ 20 | 'translations', 21 | ]; 22 | 23 | /** @var array */ 24 | protected $indexForm = [ 25 | 'translations', 26 | ]; 27 | 28 | /** @var array */ 29 | protected $indexColumns = [ 30 | 'title' => [ 31 | 'title' => 'Title', 32 | 'field' => 'title', 33 | 'sort' => true, 34 | ], 35 | 'publishDate' => [ 36 | 'title' => 'Publish Date', 37 | 'field' => 'publishDate', 38 | 'present' => true, 39 | ], 40 | ]; 41 | } 42 | -------------------------------------------------------------------------------- /src/Http/Controllers/Admin/ResponseController.php: -------------------------------------------------------------------------------- 1 | */ 12 | protected $indexOptions = [ 13 | 'create' => false, 14 | 'edit' => true, 15 | 'publish' => false, 16 | 'bulkPublish' => false, 17 | 'feature' => false, 18 | 'bulkFeature' => false, 19 | 'restore' => true, 20 | 'bulkRestore' => true, 21 | 'delete' => true, 22 | 'bulkDelete' => true, 23 | 'reorder' => false, 24 | 'permalink' => false, 25 | 'bulkEdit' => true, 26 | 'editInModal' => false, 27 | 'forceDelete' => true, 28 | 'bulkForceDelete' => true, 29 | ]; 30 | 31 | /** @var array */ 32 | protected $indexColumns = [ 33 | 'title' => [ 34 | 'title' => 'Title', 35 | 'field' => 'title', 36 | 'sort' => true, 37 | ], 38 | 'form' => [ 39 | 'title' => 'Form', 40 | 'field' => 'form', 41 | 'present' => true, 42 | ], 43 | 'date' => [ 44 | 'title' => 'Date', 45 | 'field' => 'created_at', 46 | 'sort' => true, 47 | 'present' => true, 48 | ], 49 | ]; 50 | } 51 | -------------------------------------------------------------------------------- /src/Http/Controllers/Front/CategoryController.php: -------------------------------------------------------------------------------- 1 | route('front.posts.index'); 16 | } 17 | 18 | public function show(string $slug): View 19 | { 20 | $item = Category::query() 21 | ->forSlug($slug) 22 | ->firstOrFail(); 23 | 24 | $this->seo([ 25 | 'title' => $item->title, 26 | 'description' => $item->description, 27 | 'routeName' => 'front.categories.show', 28 | 'routeArg' => 'slug', 29 | 'slug' => $slug, 30 | ]); 31 | 32 | return view('front.categories.show')->with([ 33 | 'item' => $item, 34 | ]); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /src/Http/Controllers/Front/Controller.php: -------------------------------------------------------------------------------- 1 | findOrFail($request->route('id')); 23 | 24 | $values = $request->validated()['fields']; 25 | 26 | $data = []; 27 | 28 | foreach ($form->getFieldsBySection() as $sectionIndex => $section) { 29 | $data[$sectionIndex] = [ 30 | 'title' => $section['title'] ?? null, 31 | 'fields' => [], 32 | ]; 33 | foreach ($section['fields'] as $fieldIndex => $field) { 34 | $data[$sectionIndex]['fields'][$fieldIndex] = [ 35 | 'label' => $field['label'] ?? null, 36 | 'value' => $values[$sectionIndex][$fieldIndex] ?? null, 37 | ]; 38 | } 39 | } 40 | 41 | if ($form->store) { 42 | $response = app(ResponseRepository::class)->create([ 43 | 'form_id' => $form->id, 44 | 'data' => $data, 45 | ]); 46 | } 47 | 48 | if ($form->send) { 49 | $form->recipients_collection->each(function ($recipient) use ($response) { 50 | Mail::to($recipient)->send(new FormResponseSubmitted($response)); 51 | }); 52 | } 53 | 54 | return 'ok'; 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /src/Http/Controllers/Front/NewsletterSubscriptionController.php: -------------------------------------------------------------------------------- 1 | validated(); 19 | 20 | Mailchimp::subscribe($attributes['list'], $attributes['email']); 21 | 22 | return back()->with('success', __('form.confirmSubscription')); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/Http/Controllers/Front/PageController.php: -------------------------------------------------------------------------------- 1 | seo([ 20 | 'routeName' => 'front.pages.index', 21 | ]); 22 | 23 | return view('front.pages.show')->with([ 24 | 'item' => $item, 25 | ]); 26 | } 27 | 28 | public function show(string $slug): View 29 | { 30 | $item = Page::query() 31 | ->forSlug($slug) 32 | ->publishedInListings() 33 | ->firstOrFail(); 34 | 35 | $this->seo([ 36 | 'title' => $item->title, 37 | 'description' => $item->description, 38 | 'routeName' => 'front.pages.show', 39 | 'routeArg' => 'slug', 40 | 'slug' => $slug, 41 | ]); 42 | 43 | return view('front.pages.show')->with([ 44 | 'item' => $item, 45 | ]); 46 | } 47 | 48 | public function preview(string $slug): View 49 | { 50 | $item = Page::query() 51 | ->forSlug($slug) 52 | ->firstOrFail(); 53 | 54 | $this->seo([ 55 | 'title' => $item->title, 56 | 'description' => $item->description, 57 | ]); 58 | 59 | return view('front.pages.show')->with([ 60 | 'item' => $item, 61 | ]); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/Http/Middleware/Authenticate.php: -------------------------------------------------------------------------------- 1 | expectsJson()) { 20 | return route('admin.login.form'); 21 | } 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Http/Middleware/DefaultSeoConfig.php: -------------------------------------------------------------------------------- 1 | $title, 26 | 'seotools.opengraph.defaults.title' => $title, 27 | 'seotools.opengraph.defaults.site_name' => $title, 28 | 'seotools.json-ld.defaults.title' => $title, 29 | ]); 30 | } 31 | 32 | if ('' !== ($description = strip_tags($seo['siteDescription'] ?? ''))) { 33 | Config::set([ 34 | 'seotools.meta.defaults.description' => $description, 35 | 'seotools.opengraph.defaults.description' => $description, 36 | 'seotools.json-ld.defaults.description' => $description, 37 | ]); 38 | } 39 | 40 | return $next($request); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/Http/Middleware/RedirectTrailingSlash.php: -------------------------------------------------------------------------------- 1 | getRequestUri())) { 20 | return redirect(rtrim($request->getRequestUri(), '/'), 301); 21 | } 22 | 23 | return $next($request); 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Http/Requests/Admin/CategoryRequest.php: -------------------------------------------------------------------------------- 1 | ['required', 'boolean'], 21 | 'send' => ['required', 'boolean'], 22 | 'recipients' => ['required_if:send,1', new OneEmailPerLine], 23 | ]; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Http/Requests/Admin/MenuRequest.php: -------------------------------------------------------------------------------- 1 | ['nullable', 'url'], 18 | ]; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Http/Requests/Admin/PersonRequest.php: -------------------------------------------------------------------------------- 1 | findOrFail($this->route('id')) 21 | ->getFieldsColumn('validation'); 22 | } 23 | 24 | /** 25 | * Get custom attributes for validator errors. 26 | * 27 | * @return array 28 | */ 29 | public function attributes(): array 30 | { 31 | return Form::query() 32 | ->findOrFail($this->route('id')) 33 | ->getFieldsColumn('label'); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/Http/Requests/Front/NewsletterSubscriptionRequest.php: -------------------------------------------------------------------------------- 1 | ['required', 'email'], 22 | 'list' => ['required', Rule::in(MailchimpHelper::getListsCached()->pluck('id'))], 23 | ]; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /src/Mail/FormResponseSubmitted.php: -------------------------------------------------------------------------------- 1 | response = $response; 28 | } 29 | 30 | /** 31 | * Build the message. 32 | * 33 | * @return $this 34 | */ 35 | public function build() 36 | { 37 | return $this 38 | ->subject(__('email.formResponseSubmitted.subject')) 39 | ->markdown('emails.formResponseSubmitted'); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Models/BaseModel.php: -------------------------------------------------------------------------------- 1 | orderByRaw('FIELD(id,' . implode(',', $ids) . ')'); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Models/Category.php: -------------------------------------------------------------------------------- 1 | */ 18 | protected $with = [ 19 | 'translation', 20 | ]; 21 | 22 | protected $fillable = [ 23 | 'title', 24 | 'description', 25 | ]; 26 | 27 | public $translatedAttributes = [ 28 | 'title', 29 | 'description', 30 | 'active', 31 | ]; 32 | 33 | public $slugAttributes = [ 34 | 'title', 35 | ]; 36 | 37 | public function posts(): BelongsToMany 38 | { 39 | return $this->belongsToMany(Post::class); 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/Models/CityLab.php: -------------------------------------------------------------------------------- 1 | */ 20 | protected $with = [ 21 | 'translations', 22 | ]; 23 | 24 | /** @var array */ 25 | protected $withCount = [ 26 | 'people', 27 | ]; 28 | 29 | /** @var array */ 30 | protected $fillable = [ 31 | 'published', 32 | 'name', 33 | 'description', 34 | 'position', 35 | ]; 36 | 37 | /** @var array */ 38 | public $translatedAttributes = [ 39 | 'name', 40 | 'description', 41 | 'active', 42 | ]; 43 | 44 | /** 45 | * Required when using the HasSlug trait 46 | * 47 | * @var array 48 | */ 49 | public $slugAttributes = [ 50 | 'name', 51 | ]; 52 | 53 | /** @var array */ 54 | public $mediasParams = [ 55 | 'image' => [ 56 | 'default' => [ 57 | [ 58 | 'name' => 'default', 59 | 'ratio' => 1, 60 | ], 61 | ], 62 | ], 63 | ]; 64 | 65 | public function getTitleInBrowserAttribute(): string 66 | { 67 | return $this->name; 68 | } 69 | 70 | public function people() 71 | { 72 | return $this->morphToMany(Person::class, 'personable'); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/Models/Partner.php: -------------------------------------------------------------------------------- 1 | */ 24 | protected $fillable = [ 25 | 'published', 26 | 'name', 27 | 'website', 28 | 'position', 29 | ]; 30 | 31 | /** @var array */ 32 | public $mediasParams = [ 33 | 'logo' => [ 34 | 'default' => [ 35 | [ 36 | 'name' => 'default', 37 | ], 38 | ], 39 | ], 40 | ]; 41 | 42 | public function getTitleInBrowserAttribute(): string 43 | { 44 | return $this->name; 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Models/Response.php: -------------------------------------------------------------------------------- 1 | */ 14 | protected $with = [ 15 | 'form', 16 | ]; 17 | 18 | /** @var array */ 19 | protected $casts = [ 20 | 'data' => 'array', 21 | ]; 22 | 23 | /** @var Presenter */ 24 | protected $presenterAdmin = ResponsePresenter::class; 25 | 26 | /** @var array */ 27 | protected $fillable = [ 28 | 'form_id', 29 | 'data', 30 | ]; 31 | 32 | public function form(): BelongsTo 33 | { 34 | return $this->belongsTo(Form::class); 35 | } 36 | 37 | public function attachLabelsToData(array $sections): array 38 | { 39 | $labels = $this->form->getFieldsColumn('label'); 40 | 41 | $result = []; 42 | foreach ($sections as $sectionIndex => $section) { 43 | $result[$sectionIndex] = []; 44 | foreach ($section as $fieldIndex => $field) { 45 | $result[$sectionIndex][$fieldIndex] = [ 46 | 'label' => $labels["fields.${sectionIndex}.${fieldIndex}"] ?? null, 47 | 'value' => $field, 48 | ]; 49 | } 50 | } 51 | 52 | return $result; 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/Models/Revisions/CityLabRevision.php: -------------------------------------------------------------------------------- 1 | each(fn ($location) => Cache::forget('menu.' . $location)); 16 | } 17 | 18 | /** 19 | * Handle the setting "created" event. 20 | */ 21 | public function created(Menu $menu): void 22 | { 23 | $this->clearMenuLocationCache($menu); 24 | } 25 | 26 | /** 27 | * Handle the setting "updated" event. 28 | */ 29 | public function updated(Menu $menu): void 30 | { 31 | $this->clearMenuLocationCache($menu); 32 | } 33 | 34 | /** 35 | * Handle the setting "deleted" event. 36 | */ 37 | public function deleted(Menu $menu): void 38 | { 39 | $this->clearMenuLocationCache($menu); 40 | } 41 | 42 | /** 43 | * Handle the setting "restored" event. 44 | */ 45 | public function restored(Menu $menu): void 46 | { 47 | $this->clearMenuLocationCache($menu); 48 | } 49 | 50 | /** 51 | * Handle the setting "force deleted" event. 52 | */ 53 | public function forceDeleted(Menu $menu): void 54 | { 55 | $this->clearMenuLocationCache($menu); 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/Observers/SettingTranslationObserver.php: -------------------------------------------------------------------------------- 1 | setting_id)->section; 16 | 17 | Cache::forget('settings.' . $section); 18 | 19 | if ($section === 'mailchimp') { 20 | Cache::forget('settings.mailchimp.lists'); 21 | } 22 | } 23 | 24 | /** 25 | * Handle the setting "created" event. 26 | */ 27 | public function created(SettingTranslation $settingTranslation): void 28 | { 29 | $this->clearSettingSectionCache($settingTranslation); 30 | } 31 | 32 | /** 33 | * Handle the setting "updated" event. 34 | */ 35 | public function updated(SettingTranslation $settingTranslation): void 36 | { 37 | $this->clearSettingSectionCache($settingTranslation); 38 | } 39 | 40 | /** 41 | * Handle the setting "deleted" event. 42 | */ 43 | public function deleted(SettingTranslation $settingTranslation): void 44 | { 45 | $this->clearSettingSectionCache($settingTranslation); 46 | } 47 | 48 | /** 49 | * Handle the setting "restored" event. 50 | */ 51 | public function restored(SettingTranslation $settingTranslation): void 52 | { 53 | $this->clearSettingSectionCache($settingTranslation); 54 | } 55 | 56 | /** 57 | * Handle the setting "force deleted" event. 58 | */ 59 | public function forceDeleted(SettingTranslation $settingTranslation): void 60 | { 61 | $this->clearSettingSectionCache($settingTranslation); 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /src/Presenters/PartnerPresenter.php: -------------------------------------------------------------------------------- 1 | model->hasImage('logo')) { 12 | return null; 13 | } 14 | 15 | return $this->model->image('logo', 'default', [ 16 | 'w' => $size 17 | ]); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Presenters/PersonPresenter.php: -------------------------------------------------------------------------------- 1 | model->cityLab->first()->name ?? '–'; 12 | } 13 | 14 | public function imageSrc(int $width = 96): string 15 | { 16 | if (!$this->model->hasImage('image')) { 17 | return $this->model->placeholder_avatar; 18 | } 19 | 20 | return $this->model->image('image', 'default', [ 21 | 'w' => $width 22 | ]); 23 | } 24 | 25 | public function imageLqip(): ?string 26 | { 27 | if (!$this->model->hasImage('image')) { 28 | return null; 29 | } 30 | 31 | return $this->model->lowQualityImagePlaceholder('image'); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/Presenters/PostPresenter.php: -------------------------------------------------------------------------------- 1 | model->publish_start_date ?? $this->model->created_at) 14 | ->isoFormat(__('date.format')); 15 | } 16 | 17 | public function categories(): Collection 18 | { 19 | return $this->model->categories 20 | ->map(function ($category) { 21 | return [ 22 | 'title' => $category->title, 23 | 'url' => route('front.categories.show', ['slug' => $category->slug]), 24 | ]; 25 | }); 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /src/Presenters/Presenter.php: -------------------------------------------------------------------------------- 1 | model = $model; 17 | } 18 | 19 | /** 20 | * Allow for property-style retrieval 21 | * 22 | * @return mixed 23 | */ 24 | public function __get(string $property) 25 | { 26 | if (method_exists($this, $property)) { 27 | return $this->$property(); 28 | } 29 | 30 | return $this->model->$property; 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/Presenters/ResponsePresenter.php: -------------------------------------------------------------------------------- 1 | model->form->title ?? '–'; 12 | } 13 | 14 | public function created_at(): string 15 | { 16 | return $this->model->created_at->toCookieString(); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /src/Repositories/CategoryRepository.php: -------------------------------------------------------------------------------- 1 | model = $model; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Repositories/CityLabRepository.php: -------------------------------------------------------------------------------- 1 | [ 21 | 'routePrefix' => 'people', 22 | 'titleKey' => 'name', 23 | ], 24 | ]; 25 | 26 | public function __construct(CityLab $model) 27 | { 28 | $this->model = $model; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Repositories/FormRepository.php: -------------------------------------------------------------------------------- 1 | model = $model; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Repositories/MenuRepository.php: -------------------------------------------------------------------------------- 1 | model = $model; 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/Repositories/PageRepository.php: -------------------------------------------------------------------------------- 1 | model = $model; 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /src/Repositories/PartnerRepository.php: -------------------------------------------------------------------------------- 1 | model = $model; 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /src/Repositories/PersonRepository.php: -------------------------------------------------------------------------------- 1 | [ 19 | 'routePrefix' => 'people', 20 | 'titleKey' => 'name', 21 | ], 22 | ]; 23 | 24 | public function __construct(Person $model) 25 | { 26 | $this->model = $model; 27 | } 28 | 29 | /** 30 | * @param \Illuminate\Database\Query\Builder $query 31 | * @param array $scopes 32 | * @return \Illuminate\Database\Query\Builder 33 | */ 34 | public function filter($query, array $scopes = []) 35 | { 36 | if (array_key_exists('cityLab', $scopes)) { 37 | $cityLab = CityLab::find($scopes['cityLab']); 38 | 39 | if ($cityLab) { 40 | return $cityLab->people(); 41 | } 42 | } 43 | 44 | return parent::filter($query, $scopes); 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /src/Repositories/PostRepository.php: -------------------------------------------------------------------------------- 1 | model = $model; 23 | 24 | $this->browsers = [ 25 | 'categories' => [ 26 | 'routePrefix' => 'blog', 27 | ], 28 | ]; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/Repositories/ResponseRepository.php: -------------------------------------------------------------------------------- 1 | model = $model; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /src/Rules/OneEmailPerLine.php: -------------------------------------------------------------------------------- 1 | ['required', 'email'], 14 | ]; 15 | 16 | /** 17 | * Determine if the validation rule passes. 18 | * 19 | * @param string $attribute 20 | * @param mixed $value 21 | * @return bool 22 | */ 23 | public function passes($attribute, $value): bool 24 | { 25 | $emails = preg_split('/\r\n|\r|\n/', $value); 26 | 27 | foreach ($emails as $email) { 28 | if (Validator::make(['email' => $email], $this->rules)->fails()) { 29 | return false; 30 | } 31 | } 32 | 33 | return true; 34 | } 35 | 36 | /** 37 | * Get the validation error message. 38 | * 39 | * @return string 40 | */ 41 | public function message(): string 42 | { 43 | return __('validation.email'); 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /tests/Unit/Helpers/SettingsHelperTest.php: -------------------------------------------------------------------------------- 1 | faker->word; 16 | $section = $this->faker->word; 17 | 18 | $settings = [ 19 | $field => $this->faker->word, 20 | ]; 21 | 22 | 23 | SettingsHelper::set($settings, $section); 24 | 25 | $this->assertEquals($settings[$field], SettingsHelper::get($field, $section)); 26 | $this->assertNull(SettingsHelper::get('thisFieldDoesNotExist', $section)); 27 | } 28 | 29 | /** @test */ 30 | public function it_gets_value_for_section() 31 | { 32 | $field = $this->faker->word; 33 | $section = $this->faker->word; 34 | 35 | $settings = [ 36 | $field => $this->faker->word, 37 | ]; 38 | 39 | SettingsHelper::set($settings, $section); 40 | 41 | $this->assertEquals($settings, SettingsHelper::getSection($section)); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /tests/Unit/Helpers/SocialHelperTest.php: -------------------------------------------------------------------------------- 1 | assertEmpty(SocialHelper::getNetworks()->toArray()); 17 | } 18 | 19 | /** @test */ 20 | public function it_fetches_formatted_networks() 21 | { 22 | $user = $this->faker->userName; 23 | $networks = collect(config('cms.social.networks')); 24 | 25 | SettingsHelper::set( 26 | $networks->map(fn (): string => $user)->toArray(), 27 | 'social' 28 | ); 29 | 30 | $this->assertSame( 31 | SocialHelper::getNetworks()->toArray(), 32 | $networks 33 | ->map(fn ($network) => $network['baseUrl'] . $user) 34 | ->toArray() 35 | ); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /tests/Unit/Http/Middleware/DefaultSeoConfigTest.php: -------------------------------------------------------------------------------- 1 | states('published') 18 | ->create(); 19 | 20 | SettingsHelper::set(['frontPage' => $page->id], 'site'); 21 | 22 | $config = [ 23 | 'siteTitle' => $this->faker->word, 24 | 'siteDescription' => $this->faker->paragraph, 25 | ]; 26 | 27 | SettingsHelper::set($config, 'site'); 28 | 29 | $this->visitRoute('front.pages.index') 30 | ->see('' . $config['siteTitle'] . '') 31 | ->see('') 32 | ->see('') 33 | 34 | ->see('') 35 | ->see(''); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /tests/Unit/Http/Middleware/RedirectTrailingSlashTest.php: -------------------------------------------------------------------------------- 1 | handle(Request::create($url, 'GET'), fn () => response('OK', 200)) 21 | ->getStatusCode(); 22 | } 23 | 24 | /** @test */ 25 | public function it_removes_trailing_slash_from_url() 26 | { 27 | $page = factory(Page::class) 28 | ->states('published') 29 | ->create(); 30 | 31 | $this->assertEquals(301, $this->requestWithMiddleware("/en/{$page->slug}/")); 32 | $this->assertEquals(200, $this->requestWithMiddleware("/en/{$page->slug}")); 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tests/Unit/Models/CityLabTest.php: -------------------------------------------------------------------------------- 1 | create(); 16 | 17 | $this->assertEquals($cityLab->name, $cityLab->title_in_browser); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/Unit/Models/PartnerTest.php: -------------------------------------------------------------------------------- 1 | create(); 16 | 17 | $this->assertEquals($partner->name, $partner->title_in_browser); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /tests/Unit/Models/PersonTest.php: -------------------------------------------------------------------------------- 1 | deleteDirectory('avatars'); 18 | 19 | $person = factory(Person::class)->create(); 20 | 21 | $this->assertEquals($person->placeholder_avatar, $person->present()->imageSrc); 22 | } 23 | 24 | /** @test */ 25 | public function it_fetches_the_browser_title() 26 | { 27 | $person = factory(Person::class)->create(); 28 | 29 | $this->assertEquals($person->name, $person->title_in_browser); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /tests/Unit/Presenters/PartnerPresenterTest.php: -------------------------------------------------------------------------------- 1 | create(); 17 | $image = factory(Media::class)->create([ 18 | 'width' => 200, 19 | 'height' => 200, 20 | ]); 21 | 22 | $partner->medias()->save($image, [ 23 | 'crop_x' => 0, 24 | 'crop_y' => 0, 25 | 'crop_w' => $image->width, 26 | 'crop_h' => $image->height, 27 | 'role' => 'logo', 28 | 'crop' => 'default', 29 | 'ratio' => 'default', 30 | 'lqip_data' => null, 31 | 'metadatas' => json_encode([ 32 | 'caption' => null, 33 | 'altText' => null, 34 | 'video' => null, 35 | ]), 36 | ]); 37 | 38 | $partner->load('medias'); 39 | 40 | $this->assertStringContainsString("/img/{$image->uuid}", $partner->present()->imageSrc); 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /tests/Unit/Rules/OneEmailPerLineTest.php: -------------------------------------------------------------------------------- 1 | rule = new OneEmailPerLine(); 20 | } 21 | 22 | /** 23 | * @test 24 | * @dataProvider oneEmailPerLine 25 | */ 26 | public function it_passes_valid_input(string $input) 27 | { 28 | $this->assertTrue($this->rule->passes('test', $input)); 29 | } 30 | 31 | /** 32 | * @test 33 | * @dataProvider multipleEmailsPerLine 34 | */ 35 | public function it_fails_invalid_input(string $input) 36 | { 37 | $this->assertFalse($this->rule->passes('test', $input)); 38 | } 39 | 40 | public function oneEmailPerLine(): array 41 | { 42 | return [ 43 | 'single' => ["test1@example.net"], 44 | '\n' => ["test2@example.net\ntest2@example.org"], 45 | '\r' => ["test3@example.net\rtest3@example.org"], 46 | '\r\n' => ["test4@example.net\r\ntest4@example.org"], 47 | ]; 48 | } 49 | 50 | public function multipleEmailsPerLine(): array 51 | { 52 | return [ 53 | 'space' => ["test2@example.net test2@example.org"], 54 | 'comma' => ["test3@example.net,test3@example.org"], 55 | 'tab' => ["test4@example.net\ttest4@example.org"], 56 | ]; 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /webpack.mix.js: -------------------------------------------------------------------------------- 1 | const mix = require('laravel-mix'); 2 | 3 | mix.config.fileLoaderDirs.fonts = 'fonts'; 4 | 5 | mix.webpackConfig({ 6 | devtool: mix.config.production ? 'none' : 'source-map', 7 | }); 8 | 9 | if (mix.config.production) { 10 | mix.version(); 11 | } 12 | 13 | const config = [ 14 | require('postcss-import'), 15 | require('tailwindcss')('./tailwind.config.js'), 16 | require('postcss-nested')({ 17 | bubble: ['screen'], 18 | }), 19 | ]; 20 | 21 | mix.setPublicPath('public/assets/cms') 22 | .setResourceRoot('./') 23 | 24 | .js('resources/assets/js/app.js', 'public/assets/cms') 25 | .postCss('resources/assets/css/app.pcss', 'public/assets/cms', config) 26 | .postCss('resources/assets/css/content.pcss', 'public/assets/cms', config) 27 | .extract(); 28 | --------------------------------------------------------------------------------