├── .docker └── app │ ├── Caddyfile │ ├── Dockerfile │ └── config │ └── php.ini ├── .editorconfig ├── .github ├── .kodiak.toml ├── dependabot.yml └── workflows │ └── main.yaml ├── .gitignore ├── .nvmrc ├── .prettierignore ├── CHANGELOG.md ├── LICENSE ├── Makefile ├── README.md ├── app ├── .htaccess ├── assets │ ├── front.ts │ ├── images │ │ ├── index.ts │ │ ├── logo-small.svg │ │ ├── logo.svg │ │ ├── merge.svg │ │ └── trophy.svg │ ├── scripts │ │ ├── ga.ts │ │ └── index.ts │ ├── styles │ │ ├── document.css │ │ ├── emoji.css │ │ ├── index.ts │ │ ├── main.css │ │ ├── markdown.css │ │ └── releases.css │ └── types │ │ └── alpinejs │ │ └── index.d.ts ├── bootstrap.php ├── config │ ├── app │ │ ├── components.neon │ │ ├── console.neon │ │ ├── front.neon │ │ ├── latte.neon │ │ ├── model.neon │ │ ├── parameters.neon │ │ └── services.neon │ ├── config.local.sample │ ├── config.neon │ ├── config.test.neon │ └── ext │ │ ├── contributte.neon │ │ ├── nextras.neon │ │ └── webpack.neon ├── model │ ├── AppParams.php │ ├── Caching │ │ ├── CacheCleaner.php │ │ └── CacheKeys.php │ ├── Commands │ │ ├── Addons │ │ │ ├── ChangeFeaturedCommand.php │ │ │ ├── Composer │ │ │ │ ├── CollectStatsCommand.php │ │ │ │ └── SynchronizeCommand.php │ │ │ ├── Content │ │ │ │ └── GenerateContentCommand.php │ │ │ └── Github │ │ │ │ ├── SynchronizeCommand.php │ │ │ │ ├── SynchronizeFilesCommand.php │ │ │ │ └── SynchronizeReleasesCommand.php │ │ └── BaseCommand.php │ ├── Database │ │ ├── Helpers │ │ │ ├── ComposerLinker.php │ │ │ └── GithubLinker.php │ │ ├── ORM │ │ │ ├── AbstractEntity.php │ │ │ ├── AbstractMapper.php │ │ │ ├── AbstractRepository.php │ │ │ ├── Addon │ │ │ │ ├── Addon.php │ │ │ │ ├── AddonMapper.php │ │ │ │ └── AddonRepository.php │ │ │ ├── Composer │ │ │ │ ├── Composer.php │ │ │ │ ├── ComposerMapper.php │ │ │ │ └── ComposerRepository.php │ │ │ ├── ComposerStatistics │ │ │ │ ├── ComposerStatistics.php │ │ │ │ ├── ComposerStatisticsMapper.php │ │ │ │ └── ComposerStatisticsRepository.php │ │ │ ├── EntityModel.php │ │ │ ├── Github │ │ │ │ ├── Github.php │ │ │ │ ├── GithubMapper.php │ │ │ │ └── GithubRepository.php │ │ │ ├── GithubComposer │ │ │ │ ├── GithubComposer.php │ │ │ │ ├── GithubComposerMapper.php │ │ │ │ └── GithubComposerRepository.php │ │ │ ├── GithubRelease │ │ │ │ ├── GithubRelease.php │ │ │ │ ├── GithubReleaseMapper.php │ │ │ │ └── GithubReleaseRepository.php │ │ │ └── Tag │ │ │ │ ├── Tag.php │ │ │ │ ├── TagMapper.php │ │ │ │ └── TagRepository.php │ │ └── Query │ │ │ ├── ComponettersQuery.php │ │ │ ├── LatestActivityAddonsQuery.php │ │ │ ├── LatestAddedAddonsQuery.php │ │ │ ├── LatestReleaseIdsQuery.php │ │ │ ├── OpenSearchQuery.php │ │ │ ├── QueryObject.php │ │ │ ├── RssFeedQuery.php │ │ │ └── SearchAddonsQuery.php │ ├── Exceptions │ │ ├── LogicException.php │ │ ├── Logical │ │ │ ├── InvalidArgumentException.php │ │ │ └── InvalidStateException.php │ │ ├── Runtime │ │ │ └── WebServices │ │ │ │ ├── BowerException.php │ │ │ │ ├── ComposerException.php │ │ │ │ ├── GithubException.php │ │ │ │ └── WebServiceClientException.php │ │ └── RuntimeException.php │ ├── Facade │ │ ├── AddonFacade.php │ │ ├── Cli │ │ │ └── Commands │ │ │ │ └── AddonFacade.php │ │ └── StatisticsFacade.php │ ├── Forms │ │ ├── BaseForm.php │ │ ├── Form.php │ │ ├── FormFactory.php │ │ └── InjectFormFactory.php │ ├── Routing │ │ ├── RouterFactory.php │ │ └── RouterHelper.php │ ├── Services │ │ └── Search │ │ │ ├── Search.php │ │ │ └── SearchFactory.php │ ├── Statistics.php │ ├── Templating │ │ ├── Filters │ │ │ ├── Filters.php │ │ │ ├── Helpers.php │ │ │ └── HelpersExecutor.php │ │ ├── GithubAvatar.php │ │ ├── Macros │ │ │ └── Macros.php │ │ └── TemplateFactory.php │ ├── UI │ │ ├── AbstractPresenter.php │ │ ├── BaseControl.php │ │ ├── BaseRenderControl.php │ │ └── Destination.php │ ├── Utils │ │ ├── Arrays.php │ │ └── Validators.php │ ├── WebServices │ │ ├── Composer │ │ │ ├── ComposerClient.php │ │ │ └── ComposerService.php │ │ └── Github │ │ │ ├── GithubClient.php │ │ │ └── GithubService.php │ └── Webpack │ │ └── Entries.php └── modules │ └── Front │ ├── Addon │ ├── AddonPresenter.php │ ├── Controls │ │ ├── AddonDetail │ │ │ ├── AddonDetail.php │ │ │ ├── IAddonDetailFactory.php │ │ │ └── templates │ │ │ │ ├── comments.latte │ │ │ │ ├── content.latte │ │ │ │ ├── header.latte │ │ │ │ ├── releases.latte │ │ │ │ ├── report.latte │ │ │ │ ├── sidebar.latte │ │ │ │ └── stats.latte │ │ └── FeaturedAddon │ │ │ ├── Control.php │ │ │ ├── ControlFactory.php │ │ │ ├── FeaturedAddonComponent.php │ │ │ └── templates │ │ │ ├── _shapes.latte │ │ │ └── default.latte │ └── templates │ │ ├── _shapes.latte │ │ └── detail.latte │ ├── Base │ ├── BaseAddonPresenter.php │ ├── BasePresenter.php │ ├── Controls │ │ ├── AddonList │ │ │ ├── AddonList.php │ │ │ ├── Avatar │ │ │ │ ├── AvatarComponent.php │ │ │ │ ├── Control.php │ │ │ │ ├── ControlFactory.php │ │ │ │ └── templates │ │ │ │ │ └── default.latte │ │ │ ├── CategorizedAddonList.php │ │ │ ├── Description │ │ │ │ ├── Control.php │ │ │ │ ├── ControlFactory.php │ │ │ │ ├── DescriptionComponent.php │ │ │ │ └── templates │ │ │ │ │ └── default.latte │ │ │ ├── IAddonListFactory.php │ │ │ ├── ICategorizedAddonListFactory.php │ │ │ ├── Name │ │ │ │ ├── Control.php │ │ │ │ ├── ControlFactory.php │ │ │ │ ├── NameComponent.php │ │ │ │ └── templates │ │ │ │ │ └── default.latte │ │ │ ├── Statistics │ │ │ │ ├── Control.php │ │ │ │ ├── ControlFactory.php │ │ │ │ ├── Icon.php │ │ │ │ ├── StatisticsComponent.php │ │ │ │ └── templates │ │ │ │ │ └── default.latte │ │ │ └── templates │ │ │ │ ├── categorized.list.latte │ │ │ │ └── list.latte │ │ ├── AddonMeta │ │ │ ├── AddonMeta.php │ │ │ ├── IAddonMetaFactory.php │ │ │ └── templates │ │ │ │ ├── full.latte │ │ │ │ └── short.latte │ │ ├── AddonModal │ │ │ ├── AddonModal.php │ │ │ ├── IAddonModalFactory.php │ │ │ └── templates │ │ │ │ └── modal.latte │ │ ├── FlashMessages │ │ │ ├── Control.php │ │ │ ├── ControlFactory.php │ │ │ ├── FlashMessagesComponent.php │ │ │ └── templates │ │ │ │ └── default.latte │ │ ├── Layout │ │ │ ├── Box │ │ │ │ ├── BoxComponent.php │ │ │ │ ├── Control.php │ │ │ │ ├── ControlFactory.php │ │ │ │ └── templates │ │ │ │ │ └── default.latte │ │ │ ├── Footer │ │ │ │ ├── Control.php │ │ │ │ ├── ControlFactory.php │ │ │ │ ├── FooterComponent.php │ │ │ │ ├── Heading │ │ │ │ │ ├── Control.php │ │ │ │ │ ├── ControlFactory.php │ │ │ │ │ ├── HeadingComponent.php │ │ │ │ │ └── templates │ │ │ │ │ │ └── default.latte │ │ │ │ ├── SocialLinks │ │ │ │ │ ├── Control.php │ │ │ │ │ ├── ControlFactory.php │ │ │ │ │ ├── Icon.php │ │ │ │ │ ├── SocialLink.php │ │ │ │ │ ├── SocialLinksComponent.php │ │ │ │ │ └── templates │ │ │ │ │ │ └── default.latte │ │ │ │ ├── SubscribeForm │ │ │ │ │ ├── Control.php │ │ │ │ │ ├── ControlFactory.php │ │ │ │ │ ├── Factory.php │ │ │ │ │ ├── Handler.php │ │ │ │ │ ├── InjectFactory.php │ │ │ │ │ ├── InjectHandler.php │ │ │ │ │ ├── SubscribeFormComponent.php │ │ │ │ │ └── templates │ │ │ │ │ │ └── default.latte │ │ │ │ └── templates │ │ │ │ │ ├── _componette.latte │ │ │ │ │ ├── _links.latte │ │ │ │ │ ├── _shapes.latte │ │ │ │ │ └── default.latte │ │ │ ├── Header │ │ │ │ ├── Control.php │ │ │ │ ├── ControlFactory.php │ │ │ │ ├── HeaderComponent.php │ │ │ │ ├── Menu │ │ │ │ │ ├── Control.php │ │ │ │ │ ├── ControlFactory.php │ │ │ │ │ ├── MenuComponent.php │ │ │ │ │ ├── MenuLink.php │ │ │ │ │ └── templates │ │ │ │ │ │ └── default.latte │ │ │ │ └── templates │ │ │ │ │ ├── _shapes.latte │ │ │ │ │ └── default.latte │ │ │ └── Heading │ │ │ │ ├── Control.php │ │ │ │ ├── ControlFactory.php │ │ │ │ ├── HeadingComponent.php │ │ │ │ └── templates │ │ │ │ └── default.latte │ │ ├── News │ │ │ ├── Article.php │ │ │ ├── Control.php │ │ │ ├── ControlFactory.php │ │ │ ├── InjectLoadFromRss.php │ │ │ ├── LoadFromRss.php │ │ │ ├── NewsComponent.php │ │ │ └── templates │ │ │ │ └── default.latte │ │ ├── ReleaseList │ │ │ ├── IReleaseListFactory.php │ │ │ ├── ReleaseList.php │ │ │ └── templates │ │ │ │ └── list.latte │ │ ├── Search │ │ │ ├── ISearchFactory.php │ │ │ ├── Search.php │ │ │ └── templates │ │ │ │ └── search.latte │ │ ├── Statistics │ │ │ ├── IStatisticsFactory.php │ │ │ ├── Statistics.php │ │ │ └── templates │ │ │ │ └── footer.latte │ │ ├── Status │ │ │ ├── IStatusFactory.php │ │ │ ├── Status.php │ │ │ └── templates │ │ │ │ └── status.latte │ │ ├── Svg │ │ │ ├── Control.php │ │ │ ├── ControlFactory.php │ │ │ ├── SvgComponent.php │ │ │ └── templates │ │ │ │ └── default.latte │ │ └── Tags │ │ │ ├── Control.php │ │ │ ├── ControlFactory.php │ │ │ ├── InjectTags.php │ │ │ ├── Tags.php │ │ │ ├── TagsComponent.php │ │ │ └── templates │ │ │ └── default.latte │ └── templates │ │ ├── @layout.latte │ │ └── _parts │ │ ├── @ga.latte │ │ ├── @pageinfo.latte │ │ └── @smartlook.latte │ ├── Error │ ├── ErrorPresenter.php │ └── templates │ │ ├── 403.latte │ │ ├── 404.latte │ │ ├── 405.latte │ │ ├── 410.latte │ │ ├── 4xx.latte │ │ └── 500.latte │ ├── Generator │ ├── Controls │ │ └── Sitemap │ │ │ ├── ISitemapFactory.php │ │ │ ├── Sitemap.php │ │ │ └── templates │ │ │ └── sitemap.latte │ ├── GeneratorPresenter.php │ └── templates │ │ ├── opensearch.latte │ │ └── sitemap.latte │ ├── Home │ ├── HomePresenter.php │ └── templates │ │ └── default.latte │ ├── Index │ ├── IndexPresenter.php │ └── templates │ │ ├── all.latte │ │ ├── author.latte │ │ ├── search.latte │ │ └── tag.latte │ ├── OpenSearch │ └── OpenSearchPresenter.php │ └── Rss │ ├── Controls │ └── RssFeed │ │ ├── IRssFeedFactory.php │ │ ├── RssFeed.php │ │ ├── RssFeedFactory.php │ │ └── templates │ │ └── rss.latte │ ├── RssPresenter.php │ └── templates │ ├── author.latte │ └── newest.latte ├── bin ├── console └── tester ├── composer.json ├── composer.lock ├── cron ├── daily ├── hourly ├── monthly └── weekly ├── docker-compose.dev.yml ├── docker-compose.yml ├── log └── .gitignore ├── migrations ├── basic-data │ └── 2016-08-06-002-tags.sql ├── dummy-data │ └── 2016-08-06-003-data.sql ├── run.php └── structures │ ├── 2016-08-06-001-database.sql │ ├── 2016-08-06-004-addon.sql │ ├── 2016-08-06-005-github_release.sql │ ├── 2017-01-11-001-github.sql │ ├── 2017-04-02-001-v1.2.0.sql │ ├── 2017-04-11-001-github.sql │ ├── 2018-12-26-001-composer.sql │ └── 2020-10-19-173611-featured-addon.sql ├── package-lock.json ├── package.json ├── phpstan-baseline.neon ├── phpstan.neon ├── postcss.config.js ├── prettier.config.js ├── ruleset.xml ├── tailwind.config.js ├── temp └── .gitignore ├── tests ├── .gitignore ├── bootstrap.container.php ├── bootstrap.php ├── cases │ ├── Integration │ │ ├── Latte │ │ │ └── Compiler.phpt │ │ └── Nette │ │ │ └── Container │ │ │ ├── Builder.development.phpt │ │ │ └── Builder.production.phpt │ └── Unit │ │ └── Model │ │ ├── Database │ │ └── ORM │ │ │ └── Addon │ │ │ └── Addon.regex.phpt │ │ ├── Templating │ │ └── Filters │ │ │ ├── Filters.timeAgo.phpt │ │ │ └── Helpers.isPhp.phpt │ │ └── Utils │ │ └── ArrayHash.phpt └── php-unix.ini ├── tsconfig.json ├── webpack ├── loaders │ ├── images.ts │ ├── index.ts │ ├── scripts.ts │ └── styles.ts ├── tsconfig.json ├── utils.ts ├── webpack.config.ts ├── webpack.dev.ts └── webpack.prod.ts └── www ├── .htaccess ├── .maintenance.php ├── assets ├── front │ ├── css │ │ ├── github │ │ │ └── buttons.less │ │ ├── readme │ │ │ └── github.less │ │ └── theme.less │ ├── img │ │ ├── spinner.gif │ │ ├── spinner2.gif │ │ └── spinner3.gif │ └── js │ │ └── main.js └── vendor │ ├── chosen │ ├── chosen-sprite.png │ └── chosen.less │ ├── nette │ ├── nette.ajax.js │ └── nette.forms.js │ └── octoicons │ ├── octicons-local.ttf │ ├── octicons.css │ ├── octicons.eot │ ├── octicons.less │ ├── octicons.scss │ ├── octicons.svg │ ├── octicons.ttf │ ├── octicons.woff │ └── sprockets-octicons.scss ├── favicon.ico ├── index.php ├── robots.txt └── security.txt /.docker/app/Caddyfile: -------------------------------------------------------------------------------- 1 | # Listening on 2 | 0.0.0.0 3 | 4 | # Set the document root of the site. 5 | root /srv/app/www 6 | 7 | # Compress the transmitted data 8 | gzip 9 | 10 | # Set the path to the php-fpm process. 11 | fastcgi / 127.0.0.1:9000 php 12 | 13 | # Main rewrite to route non-existent files to index.php file. 14 | rewrite { 15 | if {file} not favicon.ico 16 | to {path} {path}/ /index.php?{path}&{query} 17 | } 18 | 19 | header / { 20 | # Don't show Caddy/Gunicorn as server header. 21 | -Server 22 | 23 | # Enable HTTP Strict Transport Security (HSTS) to force clients to always 24 | # connect via HTTPS (do not use if only testing) 25 | Strict-Transport-Security "max-age=31536000;" 26 | 27 | # Enable cross-site filter (XSS) and tell browser to block detected attacks 28 | X-XSS-Protection "1; mode=block" 29 | 30 | # Prevent some browsers from MIME-sniffing a response away from the declared Content-Type 31 | X-Content-Type-Options "nosniff" 32 | 33 | # Disallow the site to be rendered within a frame (clickjacking protection) 34 | X-Frame-Options "DENY" 35 | } 36 | 37 | log stdout 38 | errors stdout 39 | on startup php-fpm --nodaemonize 40 | -------------------------------------------------------------------------------- /.docker/app/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM php:8.0-fpm-alpine 2 | 3 | # Install application dependencies 4 | RUN apk add --no-cache curl bash 5 | RUN curl https://getcaddy.com | bash -s personal http.expires,http.realip 6 | RUN docker-php-ext-install mbstring mysqli pdo pdo_mysql 7 | 8 | ADD . /srv/app 9 | ADD .docker/app/Caddyfile /etc/Caddyfile 10 | COPY .docker/app/config/php.ini /usr/local/etc/php/ 11 | 12 | WORKDIR /srv/app/ 13 | RUN chown -R www-data:www-data /srv/app 14 | 15 | CMD ["/usr/local/bin/caddy", "--conf", "/etc/Caddyfile", "--log", "stdout"] 16 | -------------------------------------------------------------------------------- /.docker/app/config/php.ini: -------------------------------------------------------------------------------- 1 | memory_limit = 512M 2 | max_execution_time = 300 3 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig is awesome: http://EditorConfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | charset = utf-8 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | indent_style = tab 11 | indent_size = tab 12 | tab_width = 4 13 | 14 | [{*.css, *.js, *.json, *.yaml, *.yml, *.md, Caddyfile}] 15 | indent_style = space 16 | indent_size = 2 17 | -------------------------------------------------------------------------------- /.github/.kodiak.toml: -------------------------------------------------------------------------------- 1 | version = 1 2 | 3 | [merge] 4 | automerge_label = "automerge" 5 | blacklist_title_regex = "^WIP.*" 6 | blacklist_labels = ["WIP"] 7 | method = "rebase" 8 | delete_branch_on_merge = true 9 | notify_on_conflict = true 10 | optimistic_updates = false 11 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: github-actions 4 | directory: "/" 5 | schedule: 6 | interval: daily 7 | labels: 8 | - "dependencies" 9 | - package-ecosystem: composer 10 | directory: "/" 11 | schedule: 12 | interval: daily 13 | labels: 14 | - "dependencies" 15 | - package-ecosystem: npm 16 | directory: "/" 17 | schedule: 18 | interval: daily 19 | labels: 20 | - "dependencies" 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # IDE 2 | .idea 3 | 4 | # Nette 5 | /app/config/config.local.neon 6 | 7 | # Assets 8 | /www/assets/css/*.css 9 | /www/dist 10 | 11 | # Front-end 12 | /www/imgs 13 | 14 | # Composer 15 | /vendor/* 16 | 17 | # NPM 18 | /node_modules 19 | 20 | # Files 21 | !.gitignore 22 | !.htaccess 23 | 24 | # Docker 25 | /.docker/data 26 | /docker-compose.override.yml 27 | -------------------------------------------------------------------------------- /.nvmrc: -------------------------------------------------------------------------------- 1 | 18 2 | -------------------------------------------------------------------------------- /.prettierignore: -------------------------------------------------------------------------------- 1 | /node_modules 2 | /temp 3 | /vendor 4 | /www/assets/vendor 5 | /www/dist 6 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2015 Contributte 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | app=app 2 | bin=vendor/bin 3 | node=node_modules/.bin 4 | prettier-pattern="**/*.{css,js,json,md,ts}" 5 | temp=temp 6 | tests=tests 7 | ts-webpack=$(node)/cross-env TS_NODE_PROJECT='webpack/tsconfig.json' TS_NODE_TRANSPILE_ONLY=true 8 | webpack=webpack 9 | dirs:=bin $(app) $(tests) 10 | 11 | # Setup 12 | install: 13 | composer install 14 | 15 | autoload: 16 | composer dump-autoload 17 | 18 | build: 19 | $(ts-webpack) NODE_ENV=production $(node)/webpack --config $(webpack)/webpack.prod.ts --progress 20 | 21 | dev: 22 | $(ts-webpack) $(node)/webpack serve --config $(webpack)/webpack.dev.ts 23 | 24 | rm-cache: 25 | rm -rf $(temp)/cache 26 | 27 | reset: rm-cache autoload 28 | 29 | serve: 30 | NETTE_DEBUG=1 php -S 0.0.0.0:8000 -t www 31 | 32 | # Tests 33 | 34 | test: 35 | $(bin)/tester -s -p php --colors 1 -C $(tests)/cases 36 | 37 | test-coverage: 38 | $(bin)/tester -s -p phpdbg --colors 1 -C -d extension=xdebug.so --coverage $(temp)/coverage.xml --coverage-src $(dirs) 39 | 40 | # QA 41 | 42 | codefixer: 43 | $(bin)/codefixer $(dirs) 44 | 45 | codesniffer: 46 | $(bin)/codesniffer $(dirs) 47 | 48 | phpstan: 49 | $(bin)/phpstan analyse 50 | 51 | prettier: 52 | $(node)/prettier --check $(prettier-pattern) 53 | 54 | prettier-fix: 55 | $(node)/prettier --write $(prettier-pattern) 56 | 57 | ts: 58 | $(node)/tsc --noEmit --project tsconfig.json 59 | 60 | fix-php: reset codefixer 61 | 62 | fix-ts: prettier-fix 63 | 64 | fix: fix-php fix-ts 65 | 66 | qa-php: codesniffer phpstan 67 | 68 | qa-ts: ts prettier 69 | 70 | qa: codesniffer phpstan ts prettier 71 | 72 | 73 | # Deploy 74 | 75 | deploy: 76 | npm ci 77 | make build 78 | composer install --prefer-dist -o --no-dev 79 | bin/console migrations:continue 80 | rm -rf temp/* 81 | -------------------------------------------------------------------------------- /app/.htaccess: -------------------------------------------------------------------------------- 1 | Order Allow,Deny 2 | Deny from all 3 | -------------------------------------------------------------------------------- /app/assets/front.ts: -------------------------------------------------------------------------------- 1 | import './images'; 2 | import './scripts'; 3 | import './styles'; 4 | -------------------------------------------------------------------------------- /app/assets/images/index.ts: -------------------------------------------------------------------------------- 1 | import './logo-small.svg'; 2 | import './logo.svg'; 3 | import './merge.svg'; 4 | import './trophy.svg'; 5 | -------------------------------------------------------------------------------- /app/assets/images/logo-small.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/assets/images/merge.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/assets/images/trophy.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /app/assets/scripts/ga.ts: -------------------------------------------------------------------------------- 1 | const init = (): void => { 2 | const links = document.querySelectorAll('a[data-ga]'); 3 | for (const link of links) { 4 | link.addEventListener('click', () => { 5 | ga( 6 | 'send', 7 | link.getAttribute('data-ga-event'), 8 | link.getAttribute('data-ga-event'), 9 | link.getAttribute('data-ga-category'), 10 | link.getAttribute('data-ga-action') 11 | ); 12 | }); 13 | } 14 | }; 15 | 16 | export default { init }; 17 | -------------------------------------------------------------------------------- /app/assets/scripts/index.ts: -------------------------------------------------------------------------------- 1 | import Alpine from 'alpinejs'; 2 | import 'nette-forms'; 3 | import 'svgxuse'; 4 | 5 | import ga from './ga'; 6 | 7 | document.addEventListener('DOMContentLoaded', () => { 8 | Alpine.start(); 9 | ga.init(); 10 | }); 11 | -------------------------------------------------------------------------------- /app/assets/styles/document.css: -------------------------------------------------------------------------------- 1 | @import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Sans+Condensed:wght@500;700&display=swap'); 2 | @import url('https://fonts.googleapis.com/css?family=IBM+Plex+Sans:400,600,700&display=swap&subset=latin-ext'); 3 | 4 | @tailwind base; 5 | @tailwind components; 6 | 7 | html { 8 | @apply font-body bg-blue-100; 9 | } 10 | 11 | @screen md { 12 | .top-menu { 13 | display: block !important; 14 | } 15 | } 16 | 17 | @layer utilities hover { 18 | .bg-no-image { 19 | background-image: none; 20 | } 21 | } 22 | 23 | .microdata { 24 | display: none !important; 25 | } 26 | 27 | @tailwind utilities; 28 | -------------------------------------------------------------------------------- /app/assets/styles/emoji.css: -------------------------------------------------------------------------------- 1 | .emoji { 2 | display: initial; 3 | height: 1.5em; 4 | width: 1.5em; 5 | } 6 | -------------------------------------------------------------------------------- /app/assets/styles/index.ts: -------------------------------------------------------------------------------- 1 | import './document.css'; 2 | import './emoji.css'; 3 | import './markdown.css'; 4 | import './releases.css'; 5 | -------------------------------------------------------------------------------- /app/assets/styles/main.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | 3 | @tailwind components; 4 | 5 | @import url('https://fonts.googleapis.com/css?family=IBM+Plex+Sans:400,600,700&display=swap&subset=latin-ext'); 6 | @import url('https://fonts.googleapis.com/css2?family=IBM+Plex+Sans+Condensed:wght@500;700&display=swap'); 7 | 8 | html { 9 | @apply font-body bg-blue-100; 10 | } 11 | 12 | @screen md { 13 | .top-menu { 14 | display: block !important; 15 | } 16 | } 17 | @layer utilities hover { 18 | .bg-no-image { 19 | background-image: none; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /app/assets/styles/releases.css: -------------------------------------------------------------------------------- 1 | .releases section a { 2 | color: #467a85; 3 | } 4 | 5 | .releases section a:hover { 6 | color: #2a4c53; 7 | } 8 | 9 | .releases section code { 10 | padding: 0.2em 0.4em; 11 | margin: 0; 12 | font-size: 85%; 13 | background-color: rgba(27, 31, 35, 0.05); 14 | border-radius: 3px; 15 | } 16 | 17 | .releases section p, 18 | .releases section ul { 19 | margin: 1em auto; 20 | } 21 | 22 | .releases section ul { 23 | list-style: disc; 24 | padding-left: 1em; 25 | } 26 | -------------------------------------------------------------------------------- /app/assets/types/alpinejs/index.d.ts: -------------------------------------------------------------------------------- 1 | declare module 'alpinejs' { 2 | declare const Alpine: { start: () => void }; 3 | export default Alpine; 4 | } 5 | -------------------------------------------------------------------------------- /app/bootstrap.php: -------------------------------------------------------------------------------- 1 | setEnvDebugMode(); 9 | 10 | $configurator->enableDebugger(__DIR__ . '/../log'); 11 | $configurator->setTempDirectory(__DIR__ . '/../temp'); 12 | 13 | $configurator->createRobotLoader() 14 | ->addDirectory(__DIR__) 15 | ->register(); 16 | 17 | $configurator->addConfig(__DIR__ . '/config/config.neon'); 18 | $configurator->addConfig(__DIR__ . '/config/config.local.neon'); 19 | 20 | return $configurator->createContainer(); 21 | -------------------------------------------------------------------------------- /app/config/app/components.neon: -------------------------------------------------------------------------------- 1 | includes: 2 | - front.neon 3 | 4 | services: 5 | - implement: App\Modules\Front\Base\Controls\AddonList\IAddonListFactory 6 | inject: true 7 | - implement: App\Modules\Front\Base\Controls\AddonList\ICategorizedAddonListFactory 8 | inject: true 9 | - App\Modules\Front\Base\Controls\Status\IStatusFactory 10 | - implement: App\Modules\Front\Addon\Controls\AddonDetail\IAddonDetailFactory 11 | inject: true 12 | - implement: App\Modules\Front\Base\Controls\AddonModal\IAddonModalFactory 13 | inject: true 14 | - implement: App\Modules\Front\Base\Controls\Search\ISearchFactory 15 | inject: true 16 | - App\Modules\Front\Base\Controls\Statistics\IStatisticsFactory 17 | - App\Modules\Front\Rss\Controls\RssFeed\RssFeedFactory 18 | - App\Modules\Front\Rss\Controls\RssFeed\IRssFeedFactory 19 | - App\Modules\Front\Generator\Controls\Sitemap\ISitemapFactory 20 | - implement: App\Modules\Front\Base\Controls\ReleaseList\IReleaseListFactory 21 | inject: true 22 | -------------------------------------------------------------------------------- /app/config/app/console.neon: -------------------------------------------------------------------------------- 1 | services: 2 | - App\Model\Commands\Addons\ChangeFeaturedCommand 3 | - App\Model\Commands\Addons\Composer\CollectStatsCommand 4 | - App\Model\Commands\Addons\Composer\SynchronizeCommand 5 | - App\Model\Commands\Addons\Content\GenerateContentCommand 6 | - App\Model\Commands\Addons\Github\SynchronizeCommand 7 | - App\Model\Commands\Addons\Github\SynchronizeFilesCommand 8 | - App\Model\Commands\Addons\Github\SynchronizeReleasesCommand 9 | -------------------------------------------------------------------------------- /app/config/app/latte.neon: -------------------------------------------------------------------------------- 1 | services: 2 | # Filters ============================================== 3 | latte.templateFactory: 4 | class: App\Model\Templating\TemplateFactory 5 | inject: true 6 | 7 | latte.latteFactory: 8 | setup: 9 | # Common 10 | - addFilter('count', ['App\Model\Templating\Filters\Filters', 'count']) 11 | - addFilter('timeAgo', ['App\Model\Templating\Filters\Filters', 'timeAgo']) 12 | - addFilter('datetime', ['App\Model\Templating\Filters\Filters', 'datetime']) 13 | - addFilter('ucfirst', ['App\Model\Templating\Filters\Filters', 'ucfirst']) 14 | - addFilter('emojify', ['App\Model\Templating\Filters\Filters', 'emojify']) 15 | 16 | latte: 17 | macros: 18 | - App\Model\Templating\Macros\Macros 19 | -------------------------------------------------------------------------------- /app/config/app/model.neon: -------------------------------------------------------------------------------- 1 | includes: 2 | - services.neon 3 | 4 | services: 5 | 6 | # WebServices ========================================== 7 | 8 | # [github] 9 | - App\Model\WebServices\Github\GithubClient(token: %github.token%) 10 | - App\Model\WebServices\Github\GithubService 11 | 12 | # [composer] 13 | - App\Model\WebServices\Composer\ComposerClient 14 | - App\Model\WebServices\Composer\ComposerService 15 | 16 | # Search =============================================== 17 | - App\Model\Services\Search\SearchFactory 18 | - {factory: @App\Model\Services\Search\SearchFactory::create} 19 | 20 | # Facade =============================================== 21 | 22 | # [front-end] 23 | - App\Model\Facade\AddonFacade 24 | - App\Model\Facade\StatisticsFacade 25 | 26 | # [cli] 27 | - App\Model\Facade\Cli\Commands\AddonFacade 28 | 29 | # Cache ================================================ 30 | - App\Model\Caching\CacheCleaner 31 | 32 | # Routing ============================================== 33 | - App\Model\Routing\RouterHelper 34 | - {class: App\Model\Routing\RouterFactory, inject: true} 35 | routing.router: @App\Model\Routing\RouterFactory::create() 36 | 37 | # App ================================================== 38 | - App\Model\AppParams(@container::parameters) 39 | - App\Model\Statistics 40 | -------------------------------------------------------------------------------- /app/config/app/parameters.neon: -------------------------------------------------------------------------------- 1 | parameters: 2 | database: 3 | port: 3306 4 | 5 | system: 6 | error: 7 | email: null 8 | presenter: Front:Error 9 | 10 | github: 11 | token: faketoken 12 | -------------------------------------------------------------------------------- /app/config/app/services.neon: -------------------------------------------------------------------------------- 1 | services: 2 | - factory: App\Model\Forms\FormFactory 3 | inject: true 4 | -------------------------------------------------------------------------------- /app/config/config.local.sample: -------------------------------------------------------------------------------- 1 | parameters: 2 | 3 | # Database 4 | database: 5 | host: 6 | dbname: 7 | user: 8 | password: 9 | port: 3306 10 | 11 | # Error mail 12 | #system: 13 | # error: 14 | # email: 15 | 16 | # Github 17 | #github: 18 | # token: *TOKEN* 19 | 20 | services: 21 | 22 | # Disable cache 23 | #cache.storage: Nette\Caching\Storages\DevNullStorage 24 | 25 | # Disable latte cache 26 | #latte.latteFactory: 27 | # setup: 28 | # - setTempDirectory(null) 29 | 30 | #http: 31 | # proxy: 32 | # - 192.168.0.1/1 33 | 34 | #webpack: 35 | # devServer: 36 | # enabled: %debugMode% 37 | -------------------------------------------------------------------------------- /app/config/config.neon: -------------------------------------------------------------------------------- 1 | php: 2 | date.timezone: Europe/Prague 3 | # session.save_path: %tempDir%/session 4 | # zlib.output_compression: true 5 | 6 | parameters: 7 | database: 8 | driver: mysqli 9 | 10 | includes: 11 | # APPLICATION == 12 | - app/components.neon 13 | - app/console.neon 14 | - app/latte.neon 15 | - app/model.neon 16 | - app/parameters.neon 17 | 18 | # EXTENSIONS === 19 | - ext/contributte.neon 20 | - ext/nextras.neon 21 | - ext/webpack.neon 22 | 23 | session: 24 | debugger: %debugMode% 25 | expiration: '+14 days' 26 | autoStart: true 27 | 28 | application: 29 | catchExceptions: %productionMode% 30 | errorPresenter: %system.error.presenter% 31 | mapping: 32 | *: [App\Modules, *, *\*Presenter] 33 | 34 | http: 35 | headers: 36 | X-XSS-Protection: "1; mode=block" 37 | X-Powered-By: "componette" 38 | 39 | routing: 40 | debugger: %debugMode% 41 | 42 | di: 43 | debugger: %debugMode% 44 | 45 | tracy: 46 | email: %system.error.email% 47 | strictMode: true 48 | 49 | services: 50 | nette.userStorage: 51 | setup: 52 | - setNamespace(Componette) 53 | -------------------------------------------------------------------------------- /app/config/config.test.neon: -------------------------------------------------------------------------------- 1 | parameters: 2 | 3 | # Database (just for testing) 4 | database: 5 | host: locahost 6 | dbname: foobar 7 | user: foobar 8 | password: foobar 9 | port: 1234 10 | -------------------------------------------------------------------------------- /app/config/ext/contributte.neon: -------------------------------------------------------------------------------- 1 | extensions: 2 | console: Contributte\Console\DI\ConsoleExtension(%consoleMode%) 3 | xcache: Contributte\Cache\DI\CacheFactoryExtension 4 | curl: Contributte\Http\DI\CurlExtension 5 | nextras.queryobject: Contributte\Nextras\Orm\QueryObject\DI\NextrasQueryObjectExtension 6 | 7 | console: 8 | name: "Componette" 9 | version: "1.2.0" 10 | -------------------------------------------------------------------------------- /app/config/ext/nextras.neon: -------------------------------------------------------------------------------- 1 | extensions: 2 | dbal: Nextras\Dbal\Bridges\NetteDI\DbalExtension 3 | orm: Nextras\Orm\Bridges\NetteDI\OrmExtension 4 | migrations: Nextras\Migrations\Bridges\NetteDI\MigrationsExtension 5 | 6 | dbal: 7 | driver: %database.driver% 8 | host: %database.host% 9 | database: %database.dbname% 10 | username: %database.user% 11 | password: %database.password% 12 | connectionTz: '+2:00' 13 | port: %database.port% 14 | 15 | orm: 16 | model: App\Model\Database\ORM\EntityModel 17 | 18 | migrations: 19 | dir: %appDir%/../migrations 20 | driver: mysql 21 | dbal: nextras 22 | -------------------------------------------------------------------------------- /app/config/ext/webpack.neon: -------------------------------------------------------------------------------- 1 | extensions: 2 | webpack: Contributte\Webpack\DI\WebpackExtension(%debugMode%, %consoleMode%) 3 | 4 | webpack: 5 | build: 6 | directory: %wwwDir%/dist 7 | publicPath: dist/ 8 | devServer: 9 | enabled: %debugMode% 10 | url: http://localhost:9006 11 | manifest: 12 | name: manifest.json 13 | -------------------------------------------------------------------------------- /app/model/AppParams.php: -------------------------------------------------------------------------------- 1 | parameters = $parameters; 20 | } 21 | 22 | public function isDebug(): bool 23 | { 24 | return $this->parameters['debugMode'] === true; 25 | } 26 | 27 | /** 28 | * @param mixed|null $default 29 | * @return mixed 30 | */ 31 | public function get(string $name, $default = null) 32 | { 33 | if (func_num_args() > 1) { 34 | return Arrays::get($this->parameters, $name, $default); 35 | } else { 36 | return Arrays::get($this->parameters, $name); 37 | } 38 | } 39 | 40 | /** 41 | * @return mixed 42 | */ 43 | public function expand(string $name, bool $recursive = false) 44 | { 45 | return Helpers::expand('%' . $name . '%', $this->parameters, $recursive); 46 | } 47 | 48 | } 49 | -------------------------------------------------------------------------------- /app/model/Caching/CacheCleaner.php: -------------------------------------------------------------------------------- 1 | storage = $storage; 17 | } 18 | 19 | /** 20 | * Clean whole cache 21 | */ 22 | public function clean(): void 23 | { 24 | $this->storage->clean([Cache::ALL => true]); 25 | } 26 | 27 | /** 28 | * Clean by given tags 29 | * 30 | * @param string[] $tags 31 | */ 32 | public function cleanByTags(array $tags): void 33 | { 34 | $this->storage->clean([Cache::TAGS => $tags]); 35 | } 36 | 37 | /** 38 | * Clear by priority 39 | */ 40 | public function cleanByPriority(int $priority): void 41 | { 42 | $this->storage->clean([Cache::PRIORITY => intval($priority)]); 43 | } 44 | 45 | /** 46 | * Custom clear by 47 | * 48 | * @param string[] $conditions 49 | */ 50 | public function cleanBy(array $conditions): void 51 | { 52 | $this->storage->clean($conditions); 53 | } 54 | 55 | } 56 | -------------------------------------------------------------------------------- /app/model/Caching/CacheKeys.php: -------------------------------------------------------------------------------- 1 | repository = $repository; 28 | $this->em = $em; 29 | } 30 | 31 | protected function execute(InputInterface $input, OutputInterface $output): int 32 | { 33 | $next = $this->repository->getBy(['featuredAt' => null]); 34 | if (!$next) { 35 | /** @var Addon $next */ 36 | $next = $this->repository 37 | ->findBy([]) 38 | ->limitBy(1) 39 | ->orderBy('featuredAt', ICollection::ASC) 40 | ->fetch(); 41 | } 42 | 43 | $next->featuredAt = new DateTimeImmutable(); 44 | $this->em->persistAndFlush($next); 45 | $output->writeln(sprintf('Addon ID %d is now featured.', $next->id)); 46 | return 0; 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /app/model/Commands/BaseCommand.php: -------------------------------------------------------------------------------- 1 | composer = $composer; 19 | } 20 | 21 | public function getPackageUrl(?string $package = null): string 22 | { 23 | $url = new Url(self::PACKAGIST . '/packages'); 24 | $url->appendPath('/'); 25 | 26 | if ($package) { 27 | $url->appendPath($package); 28 | } else { 29 | $url->appendPath($this->composer->name); 30 | } 31 | 32 | return (string) $url; 33 | } 34 | 35 | public function getTagUrl(string $tag): string 36 | { 37 | return self::PACKAGIST . '/search/?tags=' . $tag; 38 | } 39 | 40 | public function getName(): string 41 | { 42 | return $this->composer->name; 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /app/model/Database/ORM/AbstractEntity.php: -------------------------------------------------------------------------------- 1 | 21 | */ 22 | public function fetchEntities(QueryObject $query): ICollection 23 | { 24 | /** @var ICollection $collection */ 25 | $collection = $this->fetch($query, QueryObject::HYDRATION_ENTITY); 26 | 27 | assert($collection instanceof ICollection); 28 | 29 | return $collection; 30 | } 31 | 32 | /** 33 | * @phpstan-return Result 34 | */ 35 | public function fetchResult(QueryObject $query): Result 36 | { 37 | /** @var Result $result */ 38 | $result = $this->fetch($query, QueryObject::HYDRATION_RESULTSET); 39 | 40 | assert($result instanceof Result); 41 | 42 | return $result; 43 | } 44 | 45 | /** 46 | * @phpstan-return array> 47 | * @return string[] 48 | */ 49 | abstract public static function getEntityClassNames(): array; 50 | 51 | } 52 | -------------------------------------------------------------------------------- /app/model/Database/ORM/Addon/AddonMapper.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | final class AddonRepository extends AbstractRepository 16 | { 17 | 18 | /** 19 | * @return string[] 20 | */ 21 | public static function getEntityClassNames(): array 22 | { 23 | return [Addon::class]; 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /app/model/Database/ORM/Composer/Composer.php: -------------------------------------------------------------------------------- 1 | linker === null) { 31 | $this->linker = new ComposerLinker($this); 32 | } 33 | 34 | return $this->linker; 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /app/model/Database/ORM/Composer/ComposerMapper.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | final class ComposerRepository extends AbstractRepository 16 | { 17 | 18 | /** 19 | * @return string[] 20 | */ 21 | public static function getEntityClassNames(): array 22 | { 23 | return [Composer::class]; 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /app/model/Database/ORM/ComposerStatistics/ComposerStatisticsMapper.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | final class ComposerStatisticsRepository extends AbstractRepository 16 | { 17 | 18 | /** 19 | * @return string[] 20 | */ 21 | public static function getEntityClassNames(): array 22 | { 23 | return [ComposerStatistics::class]; 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /app/model/Database/ORM/EntityModel.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | final class GithubRepository extends AbstractRepository 16 | { 17 | 18 | /** 19 | * @return string[] 20 | */ 21 | public static function getEntityClassNames(): array 22 | { 23 | return [Github::class]; 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /app/model/Database/ORM/GithubComposer/GithubComposerMapper.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | final class GithubComposerRepository extends AbstractRepository 16 | { 17 | 18 | /** 19 | * @return string[] 20 | */ 21 | public static function getEntityClassNames(): array 22 | { 23 | return [GithubComposer::class]; 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /app/model/Database/ORM/GithubRelease/GithubRelease.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | class GithubReleaseRepository extends AbstractRepository 16 | { 17 | 18 | /** 19 | * @return string[] 20 | */ 21 | public static function getEntityClassNames(): array 22 | { 23 | return [GithubRelease::class]; 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /app/model/Database/ORM/Tag/Tag.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | final class TagRepository extends AbstractRepository 16 | { 17 | 18 | /** 19 | * @return string[] 20 | */ 21 | public static function getEntityClassNames(): array 22 | { 23 | return [Tag::class]; 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /app/model/Database/Query/ComponettersQuery.php: -------------------------------------------------------------------------------- 1 | select('a.*') 14 | ->from('[addon]', 'a') 15 | ->andWhere('[a.state] = %s', Addon::STATE_ACTIVE) 16 | ->groupBy('[a.author]') 17 | ->orderBy('[a.id] DESC'); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /app/model/Database/Query/LatestActivityAddonsQuery.php: -------------------------------------------------------------------------------- 1 | select('g.*, a.*') 16 | ->from('[addon]', 'a') 17 | ->joinRight('[github] AS [g]', '[g.addon_id] = [a.id]') 18 | ->andWhere('[a.state] = %s', Addon::STATE_ACTIVE) 19 | ->addOrderBy('[g.pushed_at] DESC') 20 | ->addOrderBy('[a.updated_at] DESC'); 21 | 22 | if (!$this->limit) { 23 | $qb->limitBy(self::DEFAULT_LIMIT); 24 | } 25 | 26 | return $qb; 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /app/model/Database/Query/LatestAddedAddonsQuery.php: -------------------------------------------------------------------------------- 1 | select('a.*') 16 | ->from('[addon]', 'a') 17 | ->andWhere('[a.state] = %s', Addon::STATE_ACTIVE) 18 | ->orderBy('[a.created_at] DESC'); 19 | 20 | if (!$this->limit) { 21 | $qb->limitBy(self::DEFAULT_LIMIT); 22 | } 23 | 24 | return $qb; 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /app/model/Database/Query/LatestReleaseIdsQuery.php: -------------------------------------------------------------------------------- 1 | select('MAX([gr.published_at]), MAX(id) as id') 13 | ->from('[github_release]', 'gr') 14 | ->groupBy('[gr.github_id]') 15 | ->orderBy('[gr.published_at] DESC'); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /app/model/Database/Query/OpenSearchQuery.php: -------------------------------------------------------------------------------- 1 | token = $query; 22 | } 23 | 24 | public function doQuery(QueryBuilder $builder): QueryBuilder 25 | { 26 | if (!$this->token) { 27 | throw new InvalidStateException('Provide search query'); 28 | } 29 | 30 | $qb = $builder->select('g.*, a.*') 31 | ->from('[addon]', 'a') 32 | ->andWhere('[a.state] = %s', Addon::STATE_ACTIVE) 33 | ->addOrderBy('[a.rating] DESC') 34 | ->addOrderBy('[a.created_at] DESC'); 35 | 36 | $qb->joinRight('[github] AS [g]', '[g.addon_id] = [a.id]') 37 | ->andWhere('[a.author] LIKE %s OR [a.name] LIKE %s', '%' . $this->token . '%', '%' . $this->token . '%') 38 | ->groupBy('[a.id]'); 39 | 40 | return $qb; 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /app/model/Database/Query/QueryObject.php: -------------------------------------------------------------------------------- 1 | limit = $limit; 22 | 23 | return $this; 24 | } 25 | 26 | /** 27 | * @return static 28 | */ 29 | public function setOffset(int $offset): self 30 | { 31 | $this->offset = $offset; 32 | 33 | return $this; 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /app/model/Database/Query/RssFeedQuery.php: -------------------------------------------------------------------------------- 1 | type = self::TYPE_LATEST; 28 | } 29 | 30 | public function byAuthor(string $author): void 31 | { 32 | $this->type = self::TYPE_AUTHOR; 33 | $this->author = $author; 34 | } 35 | 36 | public function doQuery(QueryBuilder $builder): QueryBuilder 37 | { 38 | $qb = $builder->select('a.*') 39 | ->from('[addon]', 'a') 40 | ->andWhere('[a.state] = %s', Addon::STATE_ACTIVE); 41 | 42 | if ($this->type === self::TYPE_LATEST) { 43 | $qb->orderBy('[a.created_at] DESC'); 44 | } elseif ($this->type === self::TYPE_AUTHOR) { 45 | $qb->andWhere('[a.author] = %s', $this->author); 46 | } else { 47 | throw new InvalidStateException('Unknown type'); 48 | } 49 | 50 | if (!$this->limit) { 51 | $qb->limitBy(self::DEFAULT_LIMIT); 52 | } 53 | 54 | return $qb; 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /app/model/Exceptions/LogicException.php: -------------------------------------------------------------------------------- 1 | response = $response; 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /app/model/Exceptions/RuntimeException.php: -------------------------------------------------------------------------------- 1 | addonRepository = $addonRepository; 17 | } 18 | 19 | public function getById(int $id): ?Addon 20 | { 21 | return $this->addonRepository->getById($id); 22 | } 23 | 24 | public function getDetail(int $id): ?Addon 25 | { 26 | return $this->addonRepository->getBy([ 27 | 'state' => Addon::STATE_ACTIVE, 28 | 'id' => $id, 29 | ]); 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /app/model/Facade/StatisticsFacade.php: -------------------------------------------------------------------------------- 1 | addonRepository = $addonRepository; 22 | $this->tagRepository = $tagRepository; 23 | } 24 | 25 | public function countAddons(): int 26 | { 27 | return $this->addonRepository 28 | ->findAll() 29 | ->countStored(); 30 | } 31 | 32 | public function countQueued(): int 33 | { 34 | return $this->addonRepository 35 | ->findBy(['state' => Addon::STATE_QUEUED]) 36 | ->countStored(); 37 | } 38 | 39 | public function countOwners(): int 40 | { 41 | $collection = $this->addonRepository 42 | ->findAll() 43 | ->fetchPairs('author'); 44 | 45 | return count($collection); 46 | } 47 | 48 | public function countTags(): int 49 | { 50 | return $this->tagRepository 51 | ->findAll() 52 | ->countStored(); 53 | } 54 | 55 | /** 56 | * @return ICollection|Addon[] 57 | */ 58 | public function findNewest() 59 | { 60 | return $this->addonRepository 61 | ->findAll() 62 | ->orderBy('createdAt', 'DESC') 63 | ->limitBy(5); 64 | } 65 | 66 | /** 67 | * @return ICollection|Addon[] 68 | */ 69 | public function findMostPopular() 70 | { 71 | return $this->addonRepository 72 | ->findAll() 73 | ->orderBy('popularity DESC') 74 | ->limitBy(5); 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /app/model/Forms/BaseForm.php: -------------------------------------------------------------------------------- 1 | addProtection(); 16 | return $form; 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /app/model/Forms/InjectFormFactory.php: -------------------------------------------------------------------------------- 1 | formFactory = $formFactory; 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /app/model/Routing/RouterFactory.php: -------------------------------------------------------------------------------- 1 | withModule('Front') 23 | ->addRoute('sitemap.xml', 'Generator:sitemap') 24 | ->addRoute('opensearch.xml', 'Generator:opensearch') 25 | ->addRoute('api/v1/opensearch/suggest', 'OpenSearch:suggest') 26 | ->addRoute('rss/new.xml', 'Rss:newest', Route::ONE_WAY) 27 | ->addRoute('rss/latest[!.xml]', 'Rss:newest') 28 | ->addRoute('rss/[!.xml]', 'Rss:author') 29 | ->addRoute('/', [ 30 | 'presenter' => 'Addon', 31 | 'action' => 'detail', 32 | 'slug' => [ 33 | Route::FILTER_IN => [$this->addonsHelper, 'addonIn'], 34 | Route::FILTER_OUT => [$this->addonsHelper, 'addonOut'], 35 | ], 36 | ]) 37 | ->addRoute('/', [ 38 | 'presenter' => 'Index', 39 | 'action' => 'author', 40 | 'slug' => [ 41 | Route::FILTER_IN => [$this->addonsHelper, 'authorIn'], 42 | Route::FILTER_OUT => [$this->addonsHelper, 'authorOut'], 43 | ], 44 | ]) 45 | ->addRoute('', 'Home:default') 46 | ->addRoute('all/', 'Index:all') 47 | ->addRoute('search/', 'Index:search') 48 | ->addRoute('search/', 'Index:tag'); 49 | 50 | return $router; 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /app/model/Services/Search/Search.php: -------------------------------------------------------------------------------- 1 | request = $request; 16 | } 17 | 18 | public function create(): Search 19 | { 20 | $search = new Search(); 21 | 22 | // Pass parameters from request 23 | $search->by = $this->request->getQuery('search-by'); 24 | $search->limit = $this->request->getQuery('search-limit'); 25 | $search->q = $this->request->getQuery('q') ?? null; 26 | 27 | return $search; 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /app/model/Statistics.php: -------------------------------------------------------------------------------- 1 | addonRepository = $addonRepository; 26 | $this->cache = $cacheFactory->create(CacheKeys::FRONT_CONTROLS_STATISTICS); 27 | $this->build(); 28 | } 29 | 30 | /** 31 | * Build cache 32 | */ 33 | protected function build(): void 34 | { 35 | $this->cached = $this->cache->load('cached', function (&$dependencies) { 36 | $dependencies[Cache::EXPIRE] = new DateTime('+1 day'); 37 | $cached = []; 38 | 39 | // Addons counts 40 | $cached['addons'] = $this->addonRepository->findAll()->countStored(); 41 | 42 | return $cached; 43 | }); 44 | } 45 | 46 | public function getAddonsCount(): int 47 | { 48 | return $this->cached['addons']; 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /app/model/Templating/Filters/Helpers.php: -------------------------------------------------------------------------------- 1 | */ 11 | private $helpers = []; 12 | 13 | /** 14 | * @param string $name 15 | * @param callable $callback 16 | * @return void 17 | */ 18 | public function addHelper($name, callable $callback): void 19 | { 20 | $this->helpers[$name] = $callback; 21 | } 22 | 23 | /** 24 | * @param string $name 25 | * @param array $args 26 | * @return mixed 27 | */ 28 | public function __call($name, $args) 29 | { 30 | if (!isset($this->helpers[$name])) { 31 | throw new InvalidArgumentException(sprintf('Uknown helper "%s"', $name)); 32 | } 33 | 34 | return call_user_func_array($this->helpers[$name], $args); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /app/model/Templating/GithubAvatar.php: -------------------------------------------------------------------------------- 1 | addMacro('avatar', [$self, 'macroAvatar']); 22 | } 23 | 24 | /** 25 | * @param MacroNode $node 26 | * @param PhpWriter $writer 27 | * @return string 28 | */ 29 | public function macroAvatar(MacroNode $node, PhpWriter $writer): string 30 | { 31 | return $writer->write(sprintf('echo %s::generate(%%node.args)', GithubAvatar::class)); 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /app/model/Templating/TemplateFactory.php: -------------------------------------------------------------------------------- 1 | _helpers = $helpers = new HelpersExecutor(); 28 | $helpers->addHelper('isPhp', [Helpers::class, 'isPhp']); 29 | 30 | // Common variables 31 | $template->_debug = $this->appParams->isDebug(); 32 | 33 | return $template; 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /app/model/UI/AbstractPresenter.php: -------------------------------------------------------------------------------- 1 | setFile($this->getTemplateFile()); 20 | return $template; 21 | } 22 | 23 | final protected function getTemplateFile(?string $template = null): string 24 | { 25 | if (!$template) { 26 | $template = static::DEFAULT_TEMPLATE; 27 | } 28 | $file = (string)$this->getReflection()->getFileName(); 29 | return dirname($file) . "/templates/$template.latte"; 30 | } 31 | 32 | } 33 | -------------------------------------------------------------------------------- /app/model/UI/Destination.php: -------------------------------------------------------------------------------- 1 | curl = $curl; 20 | } 21 | 22 | /** 23 | * @param string[] $headers 24 | * @param string[] $opts 25 | */ 26 | public function makeRequest(string $uri, array $headers = [], array $opts = []): Response 27 | { 28 | $uri = self::URL . '/' . ltrim($uri, '/'); 29 | $response = $this->curl->makeRequest($uri, $headers, $opts); 30 | 31 | if ($response->getStatusCode() > 300) { 32 | throw new ComposerException($response); 33 | } 34 | 35 | return $response; 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /app/model/WebServices/Composer/ComposerService.php: -------------------------------------------------------------------------------- 1 | client = $client; 17 | } 18 | 19 | protected function call(string $uri): Response 20 | { 21 | try { 22 | return $this->client->makeRequest($uri); 23 | } catch (ComposerException $e) { 24 | $response = new Response(); 25 | $response->setError($e); 26 | 27 | return $response; 28 | } 29 | } 30 | 31 | public function repo(string $vendor, string $repo): Response 32 | { 33 | return $this->call(sprintf('/packages/%s/%s.json', $vendor, $repo)); 34 | } 35 | 36 | public function stats(string $vendor, string $repo, ?string $version = null): Response 37 | { 38 | if ($version) { 39 | return $this->call(sprintf('/packages/%s/%s/stats/%s.json', $vendor, $repo, $version)); 40 | } else { 41 | return $this->call(sprintf('/packages/%s/%s/stats/all.json', $vendor, $repo)); 42 | } 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /app/model/WebServices/Github/GithubClient.php: -------------------------------------------------------------------------------- 1 | curl = $curl; 26 | $this->token = $token; 27 | } 28 | 29 | public function getApiUrl(string $uri): string 30 | { 31 | return self::URL_API . '/' . trim($uri, '/'); 32 | } 33 | 34 | public function getAvatarUrl(string $username): string 35 | { 36 | return self::URL_AVATAR . '/' . trim($username, '/'); 37 | } 38 | 39 | public function getContentUrl(string $uri): string 40 | { 41 | return self::URL_CONTENT . '/' . trim($uri, '/'); 42 | } 43 | 44 | /** 45 | * @param string[] $headers 46 | * @param string[] $opts 47 | */ 48 | public function makeRequest(string $url, array $headers = [], array $opts = []): Response 49 | { 50 | if ($this->token) { 51 | $headers['Authorization'] = 'token ' . $this->token; 52 | } 53 | 54 | $response = $this->curl->makeRequest($url, $headers, $opts); 55 | 56 | if ($response->getStatusCode() > 400) { 57 | throw new GithubException($response); 58 | } 59 | 60 | return $response; 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /app/model/Webpack/Entries.php: -------------------------------------------------------------------------------- 1 | addon = $this->getAddon($slug); 26 | } 27 | 28 | public function renderDetail(): void 29 | { 30 | $this->template->addon = $this->addon; 31 | $this->template->tabs = ['content' => 'Readme', 'releases' => 'Releases', 'comments' => 'Comments']; 32 | } 33 | 34 | protected function createComponentAddon(): AddonDetail 35 | { 36 | return $this->addonDetailFactory->create($this->addon); 37 | } 38 | 39 | private function getAddon(int $id): Addon 40 | { 41 | bdump($id); 42 | if (!($addon = $this->addonFacade->getDetail($id))) { 43 | $this->error('Addon not found'); 44 | } 45 | 46 | return $addon; 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /app/modules/Front/Addon/Controls/AddonDetail/IAddonDetailFactory.php: -------------------------------------------------------------------------------- 1 | 2 |
3 | 4 | -------------------------------------------------------------------------------- /app/modules/Front/Addon/Controls/AddonDetail/templates/content.latte: -------------------------------------------------------------------------------- 1 |
2 | {var $content = $addon->github->contentHtml} 3 | {if $content} 4 | {cache $addon->id . '/content', expire => '+1 hour'} 5 | {$content|noescape} 6 | {/cache} 7 | {else} 8 |

No content at this moment.

9 | {/if} 10 |
11 | -------------------------------------------------------------------------------- /app/modules/Front/Addon/Controls/AddonDetail/templates/header.latte: -------------------------------------------------------------------------------- 1 | {varType App\Model\Database\ORM\Addon\Addon $addon} 2 |
3 |
4 | {control avatar, $addon, true} 5 |
6 | {control name, $addon, true, true} 7 | {control description, $addon} 8 |
9 |
10 |
12 |
13 | {control svg, [image => 'download-cloud-line', className => 'w-5 h-5 mr-2', fill => '8A99B0', type => 'system', size => 64]} 14 | composer require {$addon->github->masterComposer->name} 15 |
16 |
17 |
18 | -------------------------------------------------------------------------------- /app/modules/Front/Addon/Controls/AddonDetail/templates/releases.latte: -------------------------------------------------------------------------------- 1 | {block} 2 |
3 |
    4 |
  • 5 |

    6 | {$release->tag} 7 | {$release->name} 8 |

    9 |
    10 | {$release->body|noescape} 11 |
    12 |
  • 13 |
14 |

15 | No release at this moment. 16 | Try to create first one. 17 |

18 |
19 | -------------------------------------------------------------------------------- /app/modules/Front/Addon/Controls/AddonDetail/templates/report.latte: -------------------------------------------------------------------------------- 1 | {block} 2 |
3 |

4 | Is this addon outdated? Did you find an issue? Please report it. 5 |

6 |
7 | 8 | -------------------------------------------------------------------------------- /app/modules/Front/Addon/Controls/AddonDetail/templates/stats.latte: -------------------------------------------------------------------------------- 1 | {varType string $totalDownloads} 2 |
3 |
4 |
5 | -------------------------------------------------------------------------------- /app/modules/Front/Addon/Controls/FeaturedAddon/Control.php: -------------------------------------------------------------------------------- 1 | find()) { 31 | $this->template 32 | ->setParameters( 33 | [ 34 | 'addon' => $addon, 35 | ] 36 | )->render(__DIR__ . '/templates/default.latte'); 37 | } 38 | } 39 | 40 | private function find(): ?Addon 41 | { 42 | /** @var Addon|null $addon */ 43 | $addon = $this->repository 44 | ->findBy([]) 45 | ->limitBy(1) 46 | ->orderBy('featuredAt', ICollection::DESC) 47 | ->fetch(); 48 | return $addon; 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /app/modules/Front/Addon/Controls/FeaturedAddon/ControlFactory.php: -------------------------------------------------------------------------------- 1 | featuredAddonControlFactory = $controlFactory; 13 | } 14 | 15 | public function getFeaturedAddonComponent(): Control 16 | { 17 | return $this['featuredAddon']; 18 | } 19 | 20 | protected function createComponentFeaturedAddon(): Control 21 | { 22 | return $this->featuredAddonControlFactory->create(); 23 | } 24 | 25 | protected function attachComponentFeaturedAddon(Control $component): void 26 | { 27 | $this->addComponent($component, 'featuredAddon'); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /app/modules/Front/Addon/Controls/FeaturedAddon/templates/_shapes.latte: -------------------------------------------------------------------------------- 1 | 4 | 8 | 9 | -------------------------------------------------------------------------------- /app/modules/Front/Addon/Controls/FeaturedAddon/templates/default.latte: -------------------------------------------------------------------------------- 1 | {varType App\Model\Database\ORM\Addon\Addon $addon} 2 | {varType string $icon} 3 |
4 |
5 |
6 | 7 |
8 |

Package of the month

9 |
10 |
11 |
12 |
13 | {control avatar, $addon, false, true} 14 |
15 | {control name, $addon} 16 | {control description, $addon} 17 |
18 |
19 | {control statistics, $addon, true, true} 20 |
21 |
22 | {include _shapes.latte} 23 |
24 | -------------------------------------------------------------------------------- /app/modules/Front/Addon/templates/_shapes.latte: -------------------------------------------------------------------------------- 1 | 3 | 5 | 6 | -------------------------------------------------------------------------------- /app/modules/Front/Base/BaseAddonPresenter.php: -------------------------------------------------------------------------------- 1 | template->search = $this->search; 29 | } 30 | 31 | protected function createComponentModal(): AddonModal 32 | { 33 | $control = $this->addonModalFactory->create(); 34 | 35 | $control->onSuccess[] = function (): void { 36 | $this->redirect(Destination::FRONT_HOMEPAGE); 37 | }; 38 | 39 | return $control; 40 | } 41 | 42 | protected function createAddonListControl(QueryObject $queryObject): AddonList 43 | { 44 | return $this->addonListFactory->create($queryObject); 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /app/modules/Front/Base/BasePresenter.php: -------------------------------------------------------------------------------- 1 | searchFactory->create(); 39 | 40 | $search['form']->setMethod('GET'); 41 | $search['form']->setAction($this->link(Destination::FRONT_SEARCH)); 42 | 43 | $search['form']['q'] 44 | ->controlPrototype 45 | ->data('handle', $this->link(Destination::FRONT_SEARCH, ['q' => '_QUERY_'])); 46 | 47 | return $search; 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /app/modules/Front/Base/Controls/AddonList/Avatar/AvatarComponent.php: -------------------------------------------------------------------------------- 1 | avatarControlFactory = $controlFactory; 13 | } 14 | 15 | public function getAvatarComponent(): Control 16 | { 17 | return $this['avatar']; 18 | } 19 | 20 | protected function createComponentAvatar(): Control 21 | { 22 | return $this->avatarControlFactory->create(); 23 | } 24 | 25 | protected function attachComponentAvatar(Control $component): void 26 | { 27 | $this->addComponent($component, 'avatar'); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /app/modules/Front/Base/Controls/AddonList/Avatar/Control.php: -------------------------------------------------------------------------------- 1 | template->setParameters([ 14 | 'addon' => $addon, 15 | 'linkToGitHub' => $linkToGitHub, 16 | 'small' => $small, 17 | ])->render(); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /app/modules/Front/Base/Controls/AddonList/Avatar/ControlFactory.php: -------------------------------------------------------------------------------- 1 | github->linker->getRepoUrl()}{else}{plink Addon:detail, slug => $addon->id}{/if}" 5 | class="flex-shrink-0 mr-4" 6 | > 7 | {$addon->author} 9 | 10 | -------------------------------------------------------------------------------- /app/modules/Front/Base/Controls/AddonList/Description/Control.php: -------------------------------------------------------------------------------- 1 | template->setParameters(['addon' => $addon])->render(); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /app/modules/Front/Base/Controls/AddonList/Description/ControlFactory.php: -------------------------------------------------------------------------------- 1 | descriptionControlFactory = $controlFactory; 13 | } 14 | 15 | public function getDescriptionComponent(): Control 16 | { 17 | return $this['description']; 18 | } 19 | 20 | protected function createComponentDescription(): Control 21 | { 22 | return $this->descriptionControlFactory->create(); 23 | } 24 | 25 | protected function attachComponentDescription(Control $component): void 26 | { 27 | $this->addComponent($component, 'description'); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /app/modules/Front/Base/Controls/AddonList/Description/templates/default.latte: -------------------------------------------------------------------------------- 1 | {varType App\Model\Database\ORM\Addon\Addon $addon} 2 |

{$addon->github->description|truncate:150|striptags|emojify|noescape}

3 | -------------------------------------------------------------------------------- /app/modules/Front/Base/Controls/AddonList/IAddonListFactory.php: -------------------------------------------------------------------------------- 1 | template->setParameters(['addon' => $addon, 'linkToGitHub' => $linkToGitHub, 'inverseTag' => $inverseTag]); 16 | if ($github = $addon->github) { 17 | /** @var GithubRelease|null $release */ 18 | $release = $github->releases->get()->orderBy(['crawledAt' => ICollection::DESC])->fetch(); 19 | $this->template->setParameters(['release' => $release]); 20 | } else { 21 | $this->template->setParameters(['release' => null]); 22 | } 23 | $this->template->render(); 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /app/modules/Front/Base/Controls/AddonList/Name/ControlFactory.php: -------------------------------------------------------------------------------- 1 | nameControlFactory = $controlFactory; 13 | } 14 | 15 | public function getNameComponent(): Control 16 | { 17 | return $this['name']; 18 | } 19 | 20 | protected function createComponentName(): Control 21 | { 22 | return $this->nameControlFactory->create(); 23 | } 24 | 25 | protected function attachComponentName(Control $component): void 26 | { 27 | $this->addComponent($component, 'name'); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /app/modules/Front/Base/Controls/AddonList/Name/templates/default.latte: -------------------------------------------------------------------------------- 1 | {varType App\Model\Database\ORM\Addon\Addon $addon} 2 | {varType App\Model\Database\ORM\GithubRelease\GithubRelease|null $release} 3 | {varType bool $linkToGitHub} 4 | {varType bool $inverseTag} 5 |

6 | {if $linkToGitHub} 7 | 10 | {$addon->author} 11 | 12 | / 13 | 16 | {$addon->name} 17 | 18 | {else} 19 | 20 | {$addon->author} / {$addon->name} 21 | 22 | {/if} 23 | 24 | {$release->tag} 25 | 26 |

27 | -------------------------------------------------------------------------------- /app/modules/Front/Base/Controls/AddonList/Statistics/Control.php: -------------------------------------------------------------------------------- 1 | template->setParameters([ 17 | 'addon' => $addon, 18 | 'featured' => $featured, 19 | 'inline' => $inline, 20 | 'icon' => new Icon(), 21 | 'statistics' => [ 22 | 'className' => 'flex-shrink-0 w-5 h-5 mb-3 lg:mb-4 mr-2', 23 | 'image' => 'bar-chart-fill', 24 | 'fill' => 'C1CCDB', 25 | 'size' => 64, 26 | 'type' => 'business', 27 | ], 28 | ]); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /app/modules/Front/Base/Controls/AddonList/Statistics/ControlFactory.php: -------------------------------------------------------------------------------- 1 | 10 | */ 11 | public function getSvgProps(string $image, string $type): array 12 | { 13 | return [ 14 | 'className' => 'w-4 h-4', 15 | 'image' => $image, 16 | 'size' => 64, 17 | 'fill' => '467A85', 18 | 'type' => $type, 19 | ]; 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /app/modules/Front/Base/Controls/AddonList/Statistics/StatisticsComponent.php: -------------------------------------------------------------------------------- 1 | statisticsControlFactory = $controlFactory; 13 | } 14 | 15 | public function getStatisticsComponent(): Control 16 | { 17 | return $this['statistics']; 18 | } 19 | 20 | protected function createComponentStatistics(): Control 21 | { 22 | return $this->statisticsControlFactory->create(); 23 | } 24 | 25 | protected function attachComponentStatistics(Control $component): void 26 | { 27 | $this->addComponent($component, 'statistics'); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /app/modules/Front/Base/Controls/AddonList/templates/categorized.list.latte: -------------------------------------------------------------------------------- 1 | {if count($list) > 0} 2 | {foreach $list as $category => $addons} 3 |

4 | 5 | {$categories[$category]->name|ucfirst} 6 | ({=count($addons)}) 7 | 8 |

9 | {include list.latte, addons => $addons} 10 | {/foreach} 11 | {else} 12 |

No package found.

13 | {/if} 14 | -------------------------------------------------------------------------------- /app/modules/Front/Base/Controls/AddonList/templates/list.latte: -------------------------------------------------------------------------------- 1 | {varType Nextras\Orm\Collection\ICollection|array $addons} 2 | {varType string|null $title} 3 | {if count($addons)} 4 |
5 |
6 | {control heading, $title} 7 |
8 | 11 | Show all 12 | 13 |
14 | {varType App\Model\Database\ORM\Addon\Addon $addon} 15 | {foreach $addons as $addon} 16 |
17 |
18 | {control avatar, $addon} 19 |
20 | {control name, $addon} 21 | {control description, $addon} 22 |
23 |
24 | {control statistics, $addon, false, true} 25 |
26 | {/foreach} 27 | {else} 28 |

No package found.

29 | {/if} 30 | -------------------------------------------------------------------------------- /app/modules/Front/Base/Controls/AddonMeta/AddonMeta.php: -------------------------------------------------------------------------------- 1 | template->addon = $addon; 14 | $this->template->setFile(__DIR__ . '/templates/full.latte'); 15 | $this->template->render(); 16 | } 17 | 18 | public function renderShort(Addon $addon): void 19 | { 20 | $this->template->addon = $addon; 21 | $this->template->setFile(__DIR__ . '/templates/short.latte'); 22 | $this->template->render(); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /app/modules/Front/Base/Controls/AddonMeta/IAddonMetaFactory.php: -------------------------------------------------------------------------------- 1 | 'bg-red-100 border-red-400 text-red-700', 12 | 'info' => 'bg-teal-100 border-teal-400 text-teal-700', 13 | 'warning' => 'bg-orange-100 border-orange-400 text-orange-700', 14 | ]; 15 | 16 | public function render(): void 17 | { 18 | $this->template 19 | ->setParameters(['flashMessages' => $this->presenter->getFlashSession()->offsetGet('flash') ?? []]) 20 | ->render(__DIR__ . '/templates/default.latte'); 21 | } 22 | 23 | } 24 | -------------------------------------------------------------------------------- /app/modules/Front/Base/Controls/FlashMessages/ControlFactory.php: -------------------------------------------------------------------------------- 1 | flashMessagesControlFactory = $controlFactory; 13 | } 14 | 15 | public function getFlashMessagesComponent(): Control 16 | { 17 | return $this['flashMessages']; 18 | } 19 | 20 | protected function createComponentFlashMessages(): Control 21 | { 22 | return $this->flashMessagesControlFactory->create(); 23 | } 24 | 25 | protected function attachComponentFlashMessages(Control $component): void 26 | { 27 | $this->addComponent($component, 'flashMessages'); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /app/modules/Front/Base/Controls/FlashMessages/templates/default.latte: -------------------------------------------------------------------------------- 1 | {varType \stdClass[] $flashMessages} 2 |
3 |
8 | {$flash->message} 9 | 10 | 16 | Close 17 | 19 | 20 | 21 |
22 |
23 | -------------------------------------------------------------------------------- /app/modules/Front/Base/Controls/Layout/Box/BoxComponent.php: -------------------------------------------------------------------------------- 1 | boxControlFactory = $controlFactory; 13 | } 14 | 15 | public function getBoxComponent(): Control 16 | { 17 | return $this['box']; 18 | } 19 | 20 | protected function createComponentBox(): Control 21 | { 22 | return $this->boxControlFactory->create(); 23 | } 24 | 25 | protected function attachComponentBox(Control $component): void 26 | { 27 | $this->addComponent($component, 'box'); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /app/modules/Front/Base/Controls/Layout/Box/Control.php: -------------------------------------------------------------------------------- 1 | template->content = $content; 19 | $this->template->attributes = $attributes; 20 | $this->template->classNames = $classNames; 21 | $this->template->render(); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /app/modules/Front/Base/Controls/Layout/Box/ControlFactory.php: -------------------------------------------------------------------------------- 1 | 7 | {$content} 8 | 9 | -------------------------------------------------------------------------------- /app/modules/Front/Base/Controls/Layout/Footer/Control.php: -------------------------------------------------------------------------------- 1 | template 20 | ->setParameters( 21 | [ 22 | 'home' => $this->presenter->link(':Front:Home:'), 23 | 'socialLinks' => $this->socialLinks(), 24 | ] 25 | )->render(); 26 | } 27 | 28 | /** 29 | * @return SocialLink[] 30 | */ 31 | private function socialLinks(): array 32 | { 33 | return [ 34 | new SocialLink('GitHub', 'https://github.com/contributte/componette-site', 'github-fill'), 35 | new SocialLink('Twitter', 'https://twitter.com/componette', 'twitter-fill'), 36 | new SocialLink('Slack', 'https://pehapkari.slack.com', 'slack-fill'), 37 | ]; 38 | } 39 | 40 | } 41 | -------------------------------------------------------------------------------- /app/modules/Front/Base/Controls/Layout/Footer/ControlFactory.php: -------------------------------------------------------------------------------- 1 | footerControlFactory = $controlFactory; 13 | } 14 | 15 | public function getFooterComponent(): Control 16 | { 17 | return $this['footer']; 18 | } 19 | 20 | protected function createComponentFooter(): Control 21 | { 22 | return $this->footerControlFactory->create(); 23 | } 24 | 25 | protected function attachComponentFooter(Control $component): void 26 | { 27 | $this->addComponent($component, 'footer'); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /app/modules/Front/Base/Controls/Layout/Footer/Heading/Control.php: -------------------------------------------------------------------------------- 1 | template->setParameters(['text' => $text])->render(); 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /app/modules/Front/Base/Controls/Layout/Footer/Heading/ControlFactory.php: -------------------------------------------------------------------------------- 1 | headingControlFactory = $controlFactory; 13 | } 14 | 15 | public function getHeadingComponent(): Control 16 | { 17 | return $this['heading']; 18 | } 19 | 20 | protected function createComponentHeading(): Control 21 | { 22 | return $this->headingControlFactory->create(); 23 | } 24 | 25 | protected function attachComponentHeading(Control $component): void 26 | { 27 | $this->addComponent($component, 'heading'); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /app/modules/Front/Base/Controls/Layout/Footer/Heading/templates/default.latte: -------------------------------------------------------------------------------- 1 | {varType string $text} 2 |

3 | {$text} 4 |

5 | -------------------------------------------------------------------------------- /app/modules/Front/Base/Controls/Layout/Footer/SocialLinks/Control.php: -------------------------------------------------------------------------------- 1 | template->setParameters([ 20 | 'links' => $links, 21 | 'icon' => new Icon(), 22 | ])->render(); 23 | } 24 | 25 | } 26 | -------------------------------------------------------------------------------- /app/modules/Front/Base/Controls/Layout/Footer/SocialLinks/ControlFactory.php: -------------------------------------------------------------------------------- 1 | 14 | */ 15 | public function getSvgProps(SocialLink $link): array 16 | { 17 | return [ 18 | 'alt' => $link->getName(), 19 | 'className' => 'w-6 h-6 transition duration-150 ease-in-out opacity-25 group-hover:opacity-50', 20 | 'fill' => '718096', 21 | 'image' => $link->getIcon(), 22 | 'size' => 64, 23 | ]; 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /app/modules/Front/Base/Controls/Layout/Footer/SocialLinks/SocialLink.php: -------------------------------------------------------------------------------- 1 | name = $name; 24 | $this->url = $url; 25 | $this->icon = $icon; 26 | } 27 | 28 | public function getName(): string 29 | { 30 | return $this->name; 31 | } 32 | 33 | public function getUrl(): string 34 | { 35 | return $this->url; 36 | } 37 | 38 | public function getIcon(): string 39 | { 40 | return $this->icon; 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /app/modules/Front/Base/Controls/Layout/Footer/SocialLinks/SocialLinksComponent.php: -------------------------------------------------------------------------------- 1 | socialLinksControlFactory = $controlFactory; 13 | } 14 | 15 | public function getSocialLinksComponent(): Control 16 | { 17 | return $this['socialLinks']; 18 | } 19 | 20 | protected function createComponentSocialLinks(): Control 21 | { 22 | return $this->socialLinksControlFactory->create(); 23 | } 24 | 25 | protected function attachComponentSocialLinks(Control $component): void 26 | { 27 | $this->addComponent($component, 'socialLinks'); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /app/modules/Front/Base/Controls/Layout/Footer/SocialLinks/templates/default.latte: -------------------------------------------------------------------------------- 1 | {varType App\Modules\Front\Base\Controls\Layout\Footer\SocialLinks\Icon $icon} 2 | {varType App\Modules\Front\Base\Controls\Layout\Footer\SocialLinks\SocialLink[] $links} 3 |
4 | {varType App\Modules\Front\Base\Controls\Layout\Footer\SocialLinks\SocialLink $link} 5 | {foreach $links as $link} 6 | 7 | {$link->getName()} 8 | {control svg, $icon->getSvgProps($link)} 9 | 10 | {/foreach} 11 |
12 | -------------------------------------------------------------------------------- /app/modules/Front/Base/Controls/Layout/Footer/SubscribeForm/Control.php: -------------------------------------------------------------------------------- 1 | template 21 | ->setParameters( 22 | [ 23 | 'heading' =>'Subscribe to our newsletter', 24 | ] 25 | )->render(); 26 | } 27 | 28 | protected function createComponentForm(): BaseForm 29 | { 30 | $form = $this->factory->create(); 31 | $form->onSuccess[] = function (Form $form): void { 32 | $this->handler->process($form); 33 | $this->presenter->redirect('this'); 34 | }; 35 | return $form; 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /app/modules/Front/Base/Controls/Layout/Footer/SubscribeForm/ControlFactory.php: -------------------------------------------------------------------------------- 1 | formFactory->create(); 23 | $form->setAction('https://f3l1x.us4.list-manage.com/subscribe/post?u=7207f2543ed43156f473e11f0&id=0185f5988c'); 24 | $form->setHtmlAttribute('target', '_blank'); 25 | $this->email($form); 26 | $form->addSubmit(self::SUBMIT, 'Submit'); 27 | return $form; 28 | } 29 | 30 | private function email(Container $container): void 31 | { 32 | $email = $container 33 | ->addEmail(self::EMAIL) 34 | ->setRequired(); 35 | $email 36 | ->getControlPrototype() 37 | ->addAttributes( 38 | [ 39 | 'aria-label' => 'Email address', 40 | 'placeholder' => 'Your e-mail', 41 | ] 42 | ); 43 | } 44 | 45 | } 46 | -------------------------------------------------------------------------------- /app/modules/Front/Base/Controls/Layout/Footer/SubscribeForm/Handler.php: -------------------------------------------------------------------------------- 1 | values); 16 | } 17 | 18 | } 19 | -------------------------------------------------------------------------------- /app/modules/Front/Base/Controls/Layout/Footer/SubscribeForm/InjectFactory.php: -------------------------------------------------------------------------------- 1 | factory = $factory; 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /app/modules/Front/Base/Controls/Layout/Footer/SubscribeForm/InjectHandler.php: -------------------------------------------------------------------------------- 1 | handler = $handler; 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /app/modules/Front/Base/Controls/Layout/Footer/SubscribeForm/SubscribeFormComponent.php: -------------------------------------------------------------------------------- 1 | subscribeFormControlFactory = $controlFactory; 13 | } 14 | 15 | public function getSubscribeFormComponent(): Control 16 | { 17 | return $this['subscribeForm']; 18 | } 19 | 20 | protected function createComponentSubscribeForm(): Control 21 | { 22 | return $this->subscribeFormControlFactory->create(); 23 | } 24 | 25 | protected function attachComponentSubscribeForm(Control $component): void 26 | { 27 | $this->addComponent($component, 'subscribeForm'); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /app/modules/Front/Base/Controls/Layout/Footer/SubscribeForm/templates/default.latte: -------------------------------------------------------------------------------- 1 | {varType string $heading} 2 |
3 | {control heading, $heading} 4 |

5 | The latest news, articles, and resources, sent to your inbox weekly. 6 |

7 | {form form, class => 'mt-4 sm:flex sm:max-w-md'} 8 | {input App\Modules\Front\Base\Controls\Layout\Footer\SubscribeForm\Factory::EMAIL, class => 'appearance-none w-full px-5 py-2 border border-gray-300 text-base leading-6 rounded text-gray-900 bg-white placeholder-gray-500 focus:outline-none focus:placeholder-gray-400 transition duration-150 ease-in-out'} 9 |
10 | {input App\Modules\Front\Base\Controls\Layout\Footer\SubscribeForm\Factory::SUBMIT, class => 'w-full flex items-center justify-center px-6 py-2 border border-transparent text-base leading-6 font-medium rounded text-white bg-teal-600 hover:bg-teal-500 focus:outline-none focus:outline-none transition duration-150 ease-in-out'} 11 |
12 | {/form} 13 |
14 | -------------------------------------------------------------------------------- /app/modules/Front/Base/Controls/Layout/Footer/templates/_componette.latte: -------------------------------------------------------------------------------- 1 | {varType string $home} 2 |
3 | 4 | Componette 5 | Componette 6 | 7 |

8 | Addons, plugins and components for Nette Framework. 9 |

10 |
11 | -------------------------------------------------------------------------------- /app/modules/Front/Base/Controls/Layout/Footer/templates/_shapes.latte: -------------------------------------------------------------------------------- 1 | 15 | -------------------------------------------------------------------------------- /app/modules/Front/Base/Controls/Layout/Footer/templates/default.latte: -------------------------------------------------------------------------------- 1 | {varType App\Modules\Front\Base\Controls\Layout\Footer\SocialLinks\SocialLink[] $socialLinks} 2 |
3 |
4 |
5 | {include _links.latte} 6 | {control subscribeForm} 7 |
8 |
9 |
10 | This project is hosted on Váš Hosting server. Thank you for your support. 11 |
12 |
13 |
14 | {control socialLinks, $socialLinks} 15 | {include _componette.latte} 16 |
17 |
18 | {include _shapes.latte} 19 |
20 | -------------------------------------------------------------------------------- /app/modules/Front/Base/Controls/Layout/Header/Control.php: -------------------------------------------------------------------------------- 1 | template->setParameters( 17 | [ 18 | 'home' => $this->presenter->link(':Front:Home:'), 19 | 'menu' => $this->links() 20 | ] 21 | )->render(); 22 | } 23 | 24 | /** 25 | * @return array 26 | */ 27 | private function links(): array 28 | { 29 | return [ 30 | new MenuLink('Nette', 'https://pla.nette.org'), 31 | new MenuLink('Forum', 'https://forum.nette.org'), 32 | new MenuLink('Blog', 'https://blog.nette.org'), 33 | new MenuLink('Contributte', 'https://contributte.org'), 34 | new MenuLink('Sponsorship', 'https://contributte.org/partners.html'), 35 | ]; 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /app/modules/Front/Base/Controls/Layout/Header/ControlFactory.php: -------------------------------------------------------------------------------- 1 | headerControlFactory = $controlFactory; 14 | } 15 | 16 | public function getHeaderComponent(): Control 17 | { 18 | return $this['header']; 19 | } 20 | 21 | protected function createComponentHeader(): Control 22 | { 23 | return $this->headerControlFactory->create(); 24 | } 25 | 26 | protected function attachComponentHeader(Control $component): void 27 | { 28 | $this->addComponent($component, 'header'); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /app/modules/Front/Base/Controls/Layout/Header/Menu/Control.php: -------------------------------------------------------------------------------- 1 | template->links = $links; 16 | $this->template->render(); 17 | } 18 | 19 | } 20 | -------------------------------------------------------------------------------- /app/modules/Front/Base/Controls/Layout/Header/Menu/ControlFactory.php: -------------------------------------------------------------------------------- 1 | menuControlFactory = $controlFactory; 13 | } 14 | 15 | public function getMenuComponent(): Control 16 | { 17 | return $this['menu']; 18 | } 19 | 20 | protected function createComponentMenu(): Control 21 | { 22 | return $this->menuControlFactory->create(); 23 | } 24 | 25 | protected function attachComponentMenu(Control $component): void 26 | { 27 | $this->addComponent($component, 'menu'); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /app/modules/Front/Base/Controls/Layout/Header/Menu/MenuLink.php: -------------------------------------------------------------------------------- 1 | name = $name; 21 | $this->url = $url; 22 | } 23 | 24 | public function getName(): string 25 | { 26 | return $this->name; 27 | } 28 | 29 | public function getUrl(): string 30 | { 31 | return $this->url; 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /app/modules/Front/Base/Controls/Layout/Header/templates/default.latte: -------------------------------------------------------------------------------- 1 | {varType string $home} 2 | {varType App\Modules\Front\Base\Controls\Layout\Header\Menu\MenuLink[] $menu} 3 |
4 |
5 | 7 | Componette 9 |

Componette

10 |
11 | {control menu, $menu} 12 |
13 | {include _shapes.latte} 14 |
15 | -------------------------------------------------------------------------------- /app/modules/Front/Base/Controls/Layout/Heading/Control.php: -------------------------------------------------------------------------------- 1 | template->setParameters(['content' => $content, 'type' => $type])->render(); 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /app/modules/Front/Base/Controls/Layout/Heading/ControlFactory.php: -------------------------------------------------------------------------------- 1 | headingControlFactory = $controlFactory; 13 | } 14 | 15 | public function getHeadingComponent(): Control 16 | { 17 | return $this['heading']; 18 | } 19 | 20 | protected function createComponentHeading(): Control 21 | { 22 | return $this->headingControlFactory->create(); 23 | } 24 | 25 | protected function attachComponentHeading(Control $component): void 26 | { 27 | $this->addComponent($component, 'heading'); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /app/modules/Front/Base/Controls/Layout/Heading/templates/default.latte: -------------------------------------------------------------------------------- 1 | {varType string $content} 2 | {varType string $type} 3 | <{$type} class="text-xl font-bold text-gray-800 lg:text-3xl font-headers"> 4 | {$content} 5 | 6 | -------------------------------------------------------------------------------- /app/modules/Front/Base/Controls/News/Article.php: -------------------------------------------------------------------------------- 1 | title = $title; 24 | $this->description = $description; 25 | $this->link = $link; 26 | $this->date = $date; 27 | } 28 | 29 | public function getTitle(): string 30 | { 31 | return $this->title; 32 | } 33 | 34 | public function getDescription(): string 35 | { 36 | return $this->description; 37 | } 38 | 39 | public function getLink(): string 40 | { 41 | return $this->link; 42 | } 43 | 44 | public function getDate(): ?DateTime 45 | { 46 | return $this->date; 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /app/modules/Front/Base/Controls/News/Control.php: -------------------------------------------------------------------------------- 1 | template->setParameters( 17 | [ 18 | 'article' => $this->loadFromRss->process(), 19 | 'icon' => [ 20 | 'className' => 'w-4 h-4 mr-2', 21 | 'fill' => 'ffffff', 22 | 'image' => 'blaze-line', 23 | 'size' => 64, 24 | 'type' => 'weather', 25 | ], 26 | ] 27 | ); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /app/modules/Front/Base/Controls/News/ControlFactory.php: -------------------------------------------------------------------------------- 1 | loadFromRss = $loadFromRss; 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /app/modules/Front/Base/Controls/News/LoadFromRss.php: -------------------------------------------------------------------------------- 1 | load(); 20 | if ($rss) { 21 | $items = $rss->xpath('channel/item'); 22 | $latest = $items && isset($items[0]) ? $items[0] : null; 23 | if ($latest) { 24 | $article = (array)$latest->children(); 25 | return new Article($article['title'], $article['description'], $article['link'], $this->date($article)); 26 | } 27 | } 28 | 29 | return null; 30 | } 31 | 32 | /** 33 | * @param array $article 34 | */ 35 | private function date(array $article): ?DateTime 36 | { 37 | $date = DateTime::createFromFormat(DateTimeInterface::RSS, $article['pubDate']); 38 | if ($date) { 39 | return $date; 40 | } 41 | 42 | return null; 43 | } 44 | 45 | private function load(): ?SimpleXMLElement 46 | { 47 | $context = stream_context_create(['http' => ['timeout' => 5]]); 48 | $data = file_get_contents(self::FEED, false, $context); 49 | if ($data) { 50 | $xml = simplexml_load_string($data); 51 | if ($xml) { 52 | return $xml; 53 | } 54 | } 55 | 56 | return null; 57 | } 58 | 59 | } 60 | -------------------------------------------------------------------------------- /app/modules/Front/Base/Controls/News/NewsComponent.php: -------------------------------------------------------------------------------- 1 | newsControlFactory = $controlFactory; 13 | } 14 | 15 | public function getNewsComponent(): Control 16 | { 17 | return $this['news']; 18 | } 19 | 20 | protected function createComponentNews(): Control 21 | { 22 | return $this->newsControlFactory->create(); 23 | } 24 | 25 | protected function attachComponentNews(Control $component): void 26 | { 27 | $this->addComponent($component, 'news'); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /app/modules/Front/Base/Controls/News/templates/default.latte: -------------------------------------------------------------------------------- 1 | {varType App\Modules\Front\Base\Controls\News\Article|null $article} 2 | {varType array $icon} 3 |
4 |
5 | {control svg, $icon} 6 | Hot 7 |
8 |
9 |
10 |

{$article->getTitle()}

11 |

12 | {$article->getDescription()|truncate:363} 13 |

14 |
15 |
17 |
18 |
19 | {$article->getDate()->format('j.n.Y')} 20 | 24 | Read more 25 | 26 |
27 |
28 | -------------------------------------------------------------------------------- /app/modules/Front/Base/Controls/ReleaseList/IReleaseListFactory.php: -------------------------------------------------------------------------------- 1 | em = $em; 22 | } 23 | 24 | public function render(): void 25 | { 26 | $ids = $this->em->getRepositoryForEntity(GithubRelease::class) 27 | ->fetchResult(new LatestReleaseIdsQuery()) 28 | ->fetchPairs(null, 'id'); 29 | 30 | $this->template->releases = $this->em->getRepositoryForEntity(GithubRelease::class) 31 | ->findById($ids) 32 | ->orderBy('publishedAt', 'DESC') 33 | ->limitBy(15); 34 | 35 | $this->template->setFile(__DIR__ . '/templates/list.latte'); 36 | $this->template->render(); 37 | } 38 | 39 | } 40 | -------------------------------------------------------------------------------- /app/modules/Front/Base/Controls/Search/ISearchFactory.php: -------------------------------------------------------------------------------- 1 | search = $search; 24 | } 25 | 26 | /** 27 | * @param string[] $params 28 | */ 29 | public function loadState(array $params): void 30 | { 31 | if (!isset($params['by'])) { 32 | $params['by'] = $this->search->by; 33 | } 34 | 35 | parent::loadState($params); 36 | } 37 | 38 | protected function createComponentForm(): Form 39 | { 40 | $form = new Form(); 41 | 42 | $form->addText('q') 43 | ->setDefaultValue($this->search->q); 44 | $form->addSubmit('submit'); 45 | 46 | $form->onSuccess[] = function (Form $form): void { 47 | $this->search->q = $form->values->q; 48 | $this->onSearch($form->values->q); 49 | }; 50 | 51 | return $form; 52 | } 53 | 54 | public function render(): void 55 | { 56 | $this->template->setParameters([ 57 | 'searchIcon' => [ 58 | 'className' => 'h-5 md:mr-4', 59 | 'fill' => '8A99B0', 60 | 'image' => 'search-line', 61 | 'size' => 64, 62 | 'type' => 'system', 63 | ]]); 64 | $this->template->setFile(__DIR__ . '/templates/search.latte'); 65 | $this->template->render(); 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /app/modules/Front/Base/Controls/Search/templates/search.latte: -------------------------------------------------------------------------------- 1 | {varType array $searchIcon} 2 |
3 |
4 | 10 | 16 |
17 |
18 | 19 | -------------------------------------------------------------------------------- /app/modules/Front/Base/Controls/Statistics/IStatisticsFactory.php: -------------------------------------------------------------------------------- 1 | facade = $facade; 17 | } 18 | 19 | /** 20 | * RENDER ****************************************************************** 21 | */ 22 | 23 | /** 24 | * Render component 25 | */ 26 | public function renderFooter(): void 27 | { 28 | // Stats 29 | $this->template->_stats = function () { 30 | $data = []; 31 | $data['addons'] = $this->facade->countAddons(); 32 | $data['queued'] = $this->facade->countQueued(); 33 | $data['owners'] = $this->facade->countOwners(); 34 | $data['tags'] = $this->facade->countTags(); 35 | 36 | return (object) $data; 37 | }; 38 | 39 | // List of popular 40 | $this->template->_popular = function () { 41 | return $this->facade->findMostPopular(); 42 | }; 43 | 44 | // List of newest 45 | $this->template->_newest = function () { 46 | return $this->facade->findNewest(); 47 | }; 48 | 49 | $this->template->setFile(__DIR__ . '/templates/footer.latte'); 50 | $this->template->render(); 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /app/modules/Front/Base/Controls/Statistics/templates/footer.latte: -------------------------------------------------------------------------------- 1 | {block} 2 |
3 | {cache "statistics/statistics", expire => '+1 hour'} 4 | {var $stats = $_stats()} 5 |
6 |

Statistics

7 |
    8 |
  • Addons: {$stats->addons}
  • 9 |
  • Queued: {$stats->queued}
  • 10 |
  • Owners: {$stats->owners}
  • 11 |
  • Tags: {$stats->tags}
  • 12 |
13 |
14 | {/cache} 15 | 16 | {cache "statistics/popular", expire => '+1 day'} 17 | {var $popular = $_popular()} 18 |
19 |

Most popular

20 | 23 |
24 | {/cache} 25 | 26 | {cache "statistics/newest", expire => '+1 hour'} 27 | {var $newest = $_newest()} 28 |
29 |

Newest

30 | 33 |
34 | {/cache} 35 |
36 | -------------------------------------------------------------------------------- /app/modules/Front/Base/Controls/Status/IStatusFactory.php: -------------------------------------------------------------------------------- 1 | $props 14 | */ 15 | public function render(array $props): void 16 | { 17 | $this->template->setParameters(['props' => $props, 'url' => $this->url($props)])->render(); 18 | } 19 | 20 | /** 21 | * @param array $props 22 | */ 23 | private function url(array $props): string 24 | { 25 | return implode( 26 | '/', 27 | array_filter([ 28 | self::URL, 29 | $props['type'] ?? null, 30 | $props['image'] ?? null, 31 | $props['size'] ?? null, 32 | $props['fill'] ?? null, 33 | ], fn($part) => $part !== null) 34 | ); 35 | } 36 | 37 | } 38 | -------------------------------------------------------------------------------- /app/modules/Front/Base/Controls/Svg/ControlFactory.php: -------------------------------------------------------------------------------- 1 | svgControlFactory = $controlFactory; 13 | } 14 | 15 | public function getSvgComponent(): Control 16 | { 17 | return $this['svg']; 18 | } 19 | 20 | protected function createComponentSvg(): Control 21 | { 22 | return $this->svgControlFactory->create(); 23 | } 24 | 25 | protected function attachComponentSvg(Control $component): void 26 | { 27 | $this->addComponent($component, 'svg'); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /app/modules/Front/Base/Controls/Svg/templates/default.latte: -------------------------------------------------------------------------------- 1 | {varType array $props} 2 | {varType string $url} 3 | {$props['alt'] ?? $props['image']} 8 | -------------------------------------------------------------------------------- /app/modules/Front/Base/Controls/Tags/Control.php: -------------------------------------------------------------------------------- 1 | template->setParameters(['tags' => $this->tags->get()])->render(); 15 | } 16 | 17 | } 18 | -------------------------------------------------------------------------------- /app/modules/Front/Base/Controls/Tags/ControlFactory.php: -------------------------------------------------------------------------------- 1 | tags = $tags; 13 | } 14 | 15 | } 16 | -------------------------------------------------------------------------------- /app/modules/Front/Base/Controls/Tags/Tags.php: -------------------------------------------------------------------------------- 1 | em = $em; 20 | } 21 | 22 | /** 23 | * @return array 24 | */ 25 | public function get(): array 26 | { 27 | $items = $this->em->getRepositoryForEntity(Tag::class)->findAll()->fetchAll(); 28 | usort($items, function ($a, $b) { 29 | return $a->addons->countStored() <=> $b->addons->countStored(); 30 | }); 31 | return $items; 32 | } 33 | 34 | } 35 | -------------------------------------------------------------------------------- /app/modules/Front/Base/Controls/Tags/TagsComponent.php: -------------------------------------------------------------------------------- 1 | tagsControlFactory = $controlFactory; 13 | } 14 | 15 | public function getTagsComponent(): Control 16 | { 17 | return $this['tags']; 18 | } 19 | 20 | protected function createComponentTags(): Control 21 | { 22 | return $this->tagsControlFactory->create(); 23 | } 24 | 25 | protected function attachComponentTags(Control $component): void 26 | { 27 | $this->addComponent($component, 'tags'); 28 | } 29 | 30 | } 31 | -------------------------------------------------------------------------------- /app/modules/Front/Base/Controls/Tags/templates/default.latte: -------------------------------------------------------------------------------- 1 | {varType array $tags} 2 | {varType App\Model\Database\ORM\Tag\Tag $tag} 3 | 13 | -------------------------------------------------------------------------------- /app/modules/Front/Base/templates/_parts/@ga.latte: -------------------------------------------------------------------------------- 1 | {block} 2 | 13 | -------------------------------------------------------------------------------- /app/modules/Front/Base/templates/_parts/@pageinfo.latte: -------------------------------------------------------------------------------- 1 | {block} 2 | 3 | 4 | -------------------------------------------------------------------------------- /app/modules/Front/Base/templates/_parts/@smartlook.latte: -------------------------------------------------------------------------------- 1 | {block} 2 | 3 | 11 | -------------------------------------------------------------------------------- /app/modules/Front/Error/ErrorPresenter.php: -------------------------------------------------------------------------------- 1 | getCode(); 20 | // load template 403.latte or 404.latte or ... 4xx.latte 21 | $this->setView((string)(in_array($code, [403, 404, 405, 410, 500]) ? $code : '4xx')); 22 | // log to access.log 23 | $this->logger->log(sprintf( 24 | 'HTTP code %s: %s in %s:%s', 25 | $code, 26 | $exception->getMessage(), 27 | $exception->getFile(), 28 | $exception->getLine() 29 | ), 'access'); 30 | } else { 31 | $this->setView('500'); // load template 500.latte 32 | $this->logger->log($exception, ILogger::EXCEPTION); // and log exception 33 | } 34 | 35 | if ($this->isAjax()) { // AJAX request? Note this error in payload. 36 | $this->payload->error = true; 37 | $this->terminate(); 38 | } 39 | } 40 | 41 | } 42 | -------------------------------------------------------------------------------- /app/modules/Front/Error/templates/403.latte: -------------------------------------------------------------------------------- 1 | {block content} 2 |
3 |

Access Denied

4 | 5 |

You do not have permission to view this page. Please try contact the web site administrator if you believe you should be able to view this page.

6 | 7 |

8 | error 403 9 |

10 |
11 | -------------------------------------------------------------------------------- /app/modules/Front/Error/templates/404.latte: -------------------------------------------------------------------------------- 1 | {block content} 2 |
3 |

Page not found

4 | 5 |

6 | Oh no! We couldn't find the page you were looking for. 7 |

8 |
9 | -------------------------------------------------------------------------------- /app/modules/Front/Error/templates/405.latte: -------------------------------------------------------------------------------- 1 | {block content} 2 |
3 |

Method Not Allowed

4 | 5 |

The requested method is not allowed for the URL.

6 | 7 |

8 | error 405 9 |

10 |
11 | -------------------------------------------------------------------------------- /app/modules/Front/Error/templates/410.latte: -------------------------------------------------------------------------------- 1 | {block content} 2 |
3 |

Page Not Found

4 | 5 |

The page you requested has been taken off the site. We apologize for the inconvenience.

6 | 7 |

8 | error 410 9 |

10 |
11 | -------------------------------------------------------------------------------- /app/modules/Front/Error/templates/4xx.latte: -------------------------------------------------------------------------------- 1 | {block content} 2 |
3 |

Oops...

4 | 5 |

Your browser sent a request that this server could not understand or process.

6 |
7 | -------------------------------------------------------------------------------- /app/modules/Front/Error/templates/500.latte: -------------------------------------------------------------------------------- 1 | {layout none} 2 | 3 | 4 | 5 | Server Error 6 | 7 | 13 | 14 |
15 |

Server Error

16 | 17 |

We're sorry! The server encountered an internal error and 18 | was unable to complete your request. Please try again later.

19 | 20 |

error 500

21 |
22 | -------------------------------------------------------------------------------- /app/modules/Front/Generator/Controls/Sitemap/ISitemapFactory.php: -------------------------------------------------------------------------------- 1 | 3 | 4 | 5 | 6 | {$url['loc']} 7 | {$url['change']} 8 | {$url['priority']|number:2} 9 | 10 | 11 | -------------------------------------------------------------------------------- /app/modules/Front/Generator/GeneratorPresenter.php: -------------------------------------------------------------------------------- 1 | setLayout(false); 19 | $this->getHttpResponse()->setContentType('application/xml', 'utf-8'); 20 | } 21 | 22 | protected function createComponentSitemap(): Sitemap 23 | { 24 | return $this->sitemapFactory->create(); 25 | } 26 | 27 | } 28 | -------------------------------------------------------------------------------- /app/modules/Front/Generator/templates/opensearch.latte: -------------------------------------------------------------------------------- 1 | {cache rss, expire => '+ 1 week'} 2 | 3 | Componette 4 | Componette: Search for an addon 5 | Componette: Search for an addon 6 | felix@nette.org 7 | UTF-8 8 | UTF-8 9 | open 10 | {$baseUrl}/favicon.ico 11 | 12 | 13 | 14 | 15 | {/cache} 16 | -------------------------------------------------------------------------------- /app/modules/Front/Generator/templates/sitemap.latte: -------------------------------------------------------------------------------- 1 | {cache sitemap, expire => '+ 1 day'} 2 | {control sitemap} 3 | {/cache} 4 | -------------------------------------------------------------------------------- /app/modules/Front/Home/HomePresenter.php: -------------------------------------------------------------------------------- 1 | controlPrototype->autofocus = true; 30 | 31 | return $search; 32 | } 33 | 34 | protected function createComponentLatestAdded(): AddonList 35 | { 36 | return $this->createAddonListControl(new LatestAddedAddonsQuery()); 37 | } 38 | 39 | protected function createComponentLatestActivity(): AddonList 40 | { 41 | return $this->createAddonListControl(new LatestActivityAddonsQuery()); 42 | } 43 | 44 | protected function createComponentLatestReleases(): ReleaseList 45 | { 46 | return $this->releaseListFactory->create(); 47 | } 48 | 49 | } 50 | -------------------------------------------------------------------------------- /app/modules/Front/Home/templates/default.latte: -------------------------------------------------------------------------------- 1 | {block #content} 2 |
3 |
4 | {cache 'addons/lastest-activity', expire => '+1 hour'} 5 | {control latestActivity} 6 | {/cache} 7 |
8 |
9 | {cache addon-modal, expire => '+1 week'} 10 | {control modal} 11 | {/cache} 12 | 15 | All packages 16 | 17 |
18 |
19 | {cache news, expire => '+1 hour'} 20 | {control news} 21 | {/cache} 22 |
23 |
24 |
25 |
26 | {cache 'addons/latest-added', expire => '+1 hour'} 27 | {control latestAdded} 28 | {/cache} 29 |
30 |
31 | {cache 'addons/featured', expire => '+1 month'} 32 | {control featuredAddon} 33 | {/cache} 34 | {cache 'addons/latest-release', expire => '+1 hour'} 35 | {control latestReleases} 36 | {/cache} 37 |
38 |
39 | {control tags} 40 | {/block} 41 | -------------------------------------------------------------------------------- /app/modules/Front/Index/templates/all.latte: -------------------------------------------------------------------------------- 1 | {block #title}All addons{/block} 2 | {block #content} 3 |
4 | {control heading, 'All addons'} 5 |
6 | {cache 'addons/all', expire => '+1 day'} 7 | {control categorizedAddons} 8 | {/cache} 9 | {/block} 10 | -------------------------------------------------------------------------------- /app/modules/Front/Index/templates/author.latte: -------------------------------------------------------------------------------- 1 | {block #title}By author {$author}{/block} 2 | {block #content} 3 | {control addons} 4 | {/block} 5 | -------------------------------------------------------------------------------- /app/modules/Front/Index/templates/search.latte: -------------------------------------------------------------------------------- 1 | {block #title}Search for {$search->q}{/block} 2 | {block #content} 3 | {snippet search-result} 4 | {control addons} 5 | {/snippet} 6 | {/block} 7 | -------------------------------------------------------------------------------- /app/modules/Front/Index/templates/tag.latte: -------------------------------------------------------------------------------- 1 | {block #title}Tagged by {$tag}{/block} 2 | {block #content} 3 | {control addons} 4 | {/block} 5 | -------------------------------------------------------------------------------- /app/modules/Front/OpenSearch/OpenSearchPresenter.php: -------------------------------------------------------------------------------- 1 | sendJson([]); 22 | } 23 | 24 | $query = new OpenSearchQuery(); 25 | $query->byQuery($q); 26 | 27 | /** @var ICollection $addons */ 28 | $addons = $this->em->getRepositoryForEntity(Addon::class)->fetch($query, Queryable::HYDRATION_ENTITY); 29 | 30 | $output = []; 31 | $terms = []; 32 | foreach ($addons as $addon) { 33 | $terms[] = [ 34 | 'completion' => $addon->fullname, 35 | 'description' => $addon->github !== null ? $addon->github->description : '', 36 | 'link' => $this->link(':Front:Addon:detail', $addon->id), 37 | ]; 38 | } 39 | 40 | // 1st -> query string 41 | // 2nd -> completions 42 | // 3rd -> descriptions 43 | // 4th -> links 44 | $output[0] = $q; 45 | $output[1] = []; 46 | $output[2] = []; 47 | $output[3] = []; 48 | foreach ($terms as $term) { 49 | $output[1][] = $term['completion']; 50 | $output[2][] = $term['description']; 51 | $output[3][] = $term['link']; 52 | } 53 | 54 | $this->sendJson($output); 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /app/modules/Front/Rss/Controls/RssFeed/IRssFeedFactory.php: -------------------------------------------------------------------------------- 1 | em = $em; 22 | $this->rssFeedFactory = $rssFeedFactory; 23 | } 24 | 25 | public function createNewest(): RssFeed 26 | { 27 | $query = new RssFeedQuery(); 28 | $query->byLatest(); 29 | $query->setLimit(25); 30 | 31 | /** @var ICollection $list */ 32 | $list = $this->em->getRepositoryForEntity(Addon::class)->fetchEntities($query); 33 | $control = $this->rssFeedFactory->create($list); 34 | 35 | $control->setTitle('Componette - latest addons'); 36 | $control->setDescription('Latest added addons'); 37 | 38 | return $control; 39 | } 40 | 41 | public function createByAuthor(string $author): RssFeed 42 | { 43 | $query = new RssFeedQuery(); 44 | $query->byAuthor($author); 45 | 46 | /** @var ICollection $list */ 47 | $list = $this->em->getRepositoryForEntity(Addon::class)->fetchEntities($query); 48 | $control = $this->rssFeedFactory->create($list); 49 | 50 | $control->setTitle('Componette - addons by ' . $author); 51 | $control->setDescription('Addons created by ' . $author); 52 | 53 | return $control; 54 | } 55 | 56 | } 57 | -------------------------------------------------------------------------------- /app/modules/Front/Rss/Controls/RssFeed/templates/rss.latte: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | {$title} 5 | {$link} 6 | 7 | en-us 8 | {$description} 9 | {$time|date:\DateTime::RSS} 10 | {foreach $items as $item} 11 | 12 | {$item->guid} 13 | {$item->title} 14 | {$item->link} 15 | {$item->time|date:\DateTime::RSS} 16 | {$item->author} 17 | {$item->content|striptags} 18 | content|noescape}]]> 19 | 20 | {/foreach} 21 | 22 | 23 | -------------------------------------------------------------------------------- /app/modules/Front/Rss/RssPresenter.php: -------------------------------------------------------------------------------- 1 | getHttpResponse()->setContentType('application/rss+xml', 'utf-8'); 18 | 19 | $this->template->author = $author; 20 | } 21 | 22 | public function renderNewest(): void 23 | { 24 | $this->getHttpResponse()->setContentType('application/rss+xml', 'utf-8'); 25 | } 26 | 27 | protected function createComponentAuthor(): RssFeed 28 | { 29 | $control = $this->rssFeedFactory->createByAuthor($this->getParameter('author')); 30 | $control->setLink($this->link('//:Front:Home:default')); 31 | 32 | return $control; 33 | } 34 | 35 | protected function createComponentNewest(): RssFeed 36 | { 37 | $control = $this->rssFeedFactory->createNewest(); 38 | $control->setLink($this->link('//:Front:Home:default')); 39 | 40 | return $control; 41 | } 42 | 43 | } 44 | -------------------------------------------------------------------------------- /app/modules/Front/Rss/templates/author.latte: -------------------------------------------------------------------------------- 1 | {cache "rss/$author", expire => '+ 1 day'} 2 | {control author} 3 | {/cache} 4 | -------------------------------------------------------------------------------- /app/modules/Front/Rss/templates/newest.latte: -------------------------------------------------------------------------------- 1 | {cache rss, expire => '+ 1 hour'} 2 | {control newest} 3 | {/cache} 4 | -------------------------------------------------------------------------------- /bin/console: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env php 2 | getByType(Contributte\Console\Application::class)->run(); 7 | -------------------------------------------------------------------------------- /bin/tester: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # s => Show skipped files 4 | # p => Specify PHP interpreter 5 | # j => Threads (default 8) 6 | # c => php.ini file 7 | 8 | -------------------------------------------------------------------------------- /cron/daily: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | DATE=`date +%Y-%m-%d-%T` 4 | echo $DATE 5 | 6 | ../bin/console addons:github:sync all 7 | ../bin/console addons:github:sync:files all 8 | ../bin/console addons:github:sync:releases all 9 | ../bin/console addons:content:generate 10 | ../bin/console addons:composer:sync 11 | ../bin/console addons:composer:collect 12 | -------------------------------------------------------------------------------- /cron/hourly: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | DATE=`date +%Y-%m-%d` 4 | echo $DATE 5 | 6 | ../bin/console addons:github:sync --rest 7 | ../bin/console addons:github:sync:files --rest 8 | ../bin/console addons:content:generate --rest 9 | -------------------------------------------------------------------------------- /cron/monthly: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | DATE=`date +%Y-%m-%d` 4 | echo $DATE 5 | 6 | ../bin/console addons:change-featured 7 | -------------------------------------------------------------------------------- /cron/weekly: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | DATE=`date +%Y-%m-%d` 4 | echo $DATE 5 | 6 | # No cron jobs at this moment 7 | -------------------------------------------------------------------------------- /docker-compose.dev.yml: -------------------------------------------------------------------------------- 1 | version: '3.5' 2 | 3 | services: 4 | app: 5 | environment: 6 | NETTE_DEBUG: 1 7 | volumes: 8 | - ./:/srv/app:delegated 9 | 10 | db: 11 | image: mariadb:10.3 12 | environment: 13 | MYSQL_ROOT_PASSWORD: componette 14 | volumes: 15 | - ./.docker/data/db:/var/lib/mysql 16 | 17 | adminer: 18 | image: dockette/adminer:dg 19 | ports: 20 | - 8888:80 21 | -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: '3.5' 2 | 3 | services: 4 | app: 5 | build: 6 | context: . 7 | dockerfile: .docker/app/Dockerfile 8 | ports: 9 | - 8000:2015 10 | -------------------------------------------------------------------------------- /log/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /migrations/basic-data/2016-08-06-002-tags.sql: -------------------------------------------------------------------------------- 1 | SET NAMES utf8; 2 | SET time_zone = '+00:00'; 3 | SET foreign_key_checks = 0; 4 | SET sql_mode = 'NO_AUTO_VALUE_ON_ZERO'; 5 | 6 | INSERT INTO `tag` (`id`, `name`, `priority`, `color`, `highlighted`) VALUES 7 | (1, 'grid', 11, '#5AC167', 0), 8 | (5, 'datagrid', 0, NULL, 0), 9 | (6, 'images', 0, NULL, 0), 10 | (7, 'upload', 0, NULL, 0), 11 | (8, 'payments', 0, NULL, 0), 12 | (9, 'social', 13, '#2D8C61', 0), 13 | (10, 'calendar', 0, NULL, 0), 14 | (11, 'facebook', 0, NULL, 0), 15 | (12, 'twitter', 0, NULL, 0), 16 | (13, 'menu', 0, NULL, 0), 17 | (14, 'component', 0, NULL, 0), 18 | (15, 'google', 0, NULL, 0), 19 | (16, 'maps', 0, NULL, 0), 20 | (17, 'latte', 17, '#9565D1', 0), 21 | (18, 'forms', 9, '#659156', 1), 22 | (19, 'assets', 0, NULL, 0), 23 | (20, 'mail', 0, NULL, 0), 24 | (21, 'security', 7, '#ECAB55', 0), 25 | (22, 'routing', 0, NULL, 0), 26 | (23, 'api', 15, '#43C7CF', 0), 27 | (24, 'localization', 5, '#E7652D', 0), 28 | (25, 'ajax', 0, NULL, 0), 29 | (26, 'doctrine', 0, NULL, 0), 30 | (27, 'orm', 3, '#777777', 0), 31 | (28, 'database', 20, '#5089DA', 0), 32 | (29, 'tracy', 4, '#E45453', 1), 33 | (30, 'utils', 1, '#350F0F', 0), 34 | (31, 'http', 2, '#7E95B3', 0), 35 | (32, 'ide', 0, NULL, 0), 36 | (33, 'autocomplete', 0, NULL, 0), 37 | (34, 'cms', 0, NULL, 0), 38 | (35, 'sandbox', 0, NULL, 0), 39 | (36, 'console', 0, NULL, 0), 40 | (37, 'template', 0, NULL, 0), 41 | (38, 'neon', 0, NULL, 0), 42 | (39, 'pdf', 0, NULL, 0), 43 | (40, 'deploy', 0, NULL, 0), 44 | (41, 'module', 0, NULL, 0), 45 | (42, 'testing', 0, NULL, 0), 46 | (43, 'extension', 0, NULL, 0), 47 | (44, 'response', 0, NULL, 0), 48 | (45, 'annotation', 0, NULL, 0), 49 | (46, 'frontend', 0, NULL, 0), 50 | (47, 'project', 0, NULL, 0), 51 | (48, 'symfony', 0, NULL, 0); 52 | -------------------------------------------------------------------------------- /migrations/run.php: -------------------------------------------------------------------------------- 1 | getByType(Connection::class); 17 | $dbal = new NextrasAdapter($connection); 18 | $driver = new MySqlDriver($dbal); 19 | 20 | if (PHP_SAPI === 'cli') { 21 | $controller = new ConsoleController($driver); 22 | } else { 23 | $controller = new HttpController($driver); 24 | } 25 | 26 | $baseDir = __DIR__; 27 | $controller->addGroup('structures', "$baseDir/structures"); 28 | $controller->addGroup('basic-data', "$baseDir/basic-data", ['structures']); 29 | $controller->addGroup('dummy-data', "$baseDir/dummy-data", ['basic-data']); 30 | $controller->addExtension('sql', new SqlHandler($driver)); 31 | 32 | $controller->run(); 33 | -------------------------------------------------------------------------------- /migrations/structures/2016-08-06-004-addon.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `github` 2 | DROP `releases`; 3 | -------------------------------------------------------------------------------- /migrations/structures/2016-08-06-005-github_release.sql: -------------------------------------------------------------------------------- 1 | SET NAMES utf8mb4; 2 | SET time_zone = '+00:00'; 3 | SET foreign_key_checks = 0; 4 | SET sql_mode = 'NO_AUTO_VALUE_ON_ZERO'; 5 | 6 | DROP TABLE IF EXISTS `github_release`; 7 | CREATE TABLE `github_release` ( 8 | `id` int(10) unsigned NOT NULL AUTO_INCREMENT, 9 | `github_id` int(11) NOT NULL, 10 | `gid` int(11) unsigned NOT NULL, 11 | `name` varchar(255) NOT NULL, 12 | `tag` varchar(255) NOT NULL, 13 | `draft` tinyint(1) unsigned NOT NULL DEFAULT '0', 14 | `prerelease` tinyint(1) unsigned NOT NULL DEFAULT '0', 15 | `created_at` datetime NOT NULL, 16 | `published_at` datetime NOT NULL, 17 | `body` text CHARACTER SET utf8mb4 NOT NULL, 18 | `crawled_at` datetime NOT NULL, 19 | PRIMARY KEY (`id`), 20 | KEY `github_id` (`github_id`), 21 | CONSTRAINT `github_release_ibfk_1` FOREIGN KEY (`github_id`) REFERENCES `github` (`id`) 22 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 23 | -------------------------------------------------------------------------------- /migrations/structures/2017-01-11-001-github.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `github` CHANGE `content_raw` `content_raw` mediumtext COLLATE 'utf8_general_ci' NULL AFTER `description`; 2 | -------------------------------------------------------------------------------- /migrations/structures/2017-04-02-001-v1.2.0.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `addon` ADD `rating` int(11) unsigned NULL AFTER `name`; 2 | ALTER TABLE `addon` CHANGE `owner` `author` varchar(100) COLLATE 'utf8_general_ci' NOT NULL AFTER `state`; 3 | ALTER TABLE `github` CHANGE `description` `description` varchar(255) COLLATE 'utf8mb4_general_ci' NULL AFTER `addon_id`; 4 | DROP TABLE `bower`; 5 | UPDATE `addon` SET `type` = 'UNTYPE' WHERE `type` = 'BOWER'; 6 | ALTER TABLE `addon` CHANGE `type` `type` enum('COMPOSER','UNKNOWN','UNTYPE') COLLATE 'utf8_general_ci' NOT NULL DEFAULT 'UNKNOWN' AFTER `id`; 7 | 8 | SET NAMES utf8; 9 | SET time_zone = '+00:00'; 10 | SET foreign_key_checks = 0; 11 | SET sql_mode = 'NO_AUTO_VALUE_ON_ZERO'; 12 | 13 | DROP TABLE IF EXISTS `composer_statistics`; 14 | CREATE TABLE `composer_statistics` ( 15 | `id` int(11) unsigned NOT NULL AUTO_INCREMENT, 16 | `addon_id` int(11) unsigned NOT NULL, 17 | `type` enum('ALL','BRANCH','TAG') NOT NULL, 18 | `custom` varchar(100) NOT NULL, 19 | `data` blob NOT NULL, 20 | `created_at` datetime NOT NULL, 21 | `updated_at` datetime DEFAULT NULL, 22 | PRIMARY KEY (`id`), 23 | KEY `addon_id` (`addon_id`), 24 | CONSTRAINT `composer_statistics_ibfk_1` FOREIGN KEY (`addon_id`) REFERENCES `addon` (`id`) ON DELETE CASCADE 25 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 26 | 27 | DROP TABLE IF EXISTS `github_composer`; 28 | CREATE TABLE `github_composer` ( 29 | `id` int(11) unsigned NOT NULL AUTO_INCREMENT, 30 | `github_id` int(11) NOT NULL, 31 | `type` enum('BRANCH','TAG') NOT NULL, 32 | `custom` varchar(50) NOT NULL, 33 | `data` blob NOT NULL, 34 | `created_at` datetime NOT NULL, 35 | `updated_at` datetime DEFAULT NULL, 36 | PRIMARY KEY (`id`), 37 | KEY `github_id` (`github_id`), 38 | CONSTRAINT `github_composer_ibfk_1` FOREIGN KEY (`github_id`) REFERENCES `github` (`id`) ON DELETE CASCADE 39 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8; 40 | -------------------------------------------------------------------------------- /migrations/structures/2017-04-11-001-github.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `github` 2 | CHANGE `content_raw` `content_raw` mediumtext COLLATE 'utf8mb4_general_ci' NULL AFTER `description`, 3 | CHANGE `content_html` `content_html` mediumtext COLLATE 'utf8mb4_general_ci' NULL AFTER `content_raw`; 4 | -------------------------------------------------------------------------------- /migrations/structures/2018-12-26-001-composer.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `composer` 2 | CHANGE `description` `description` text COLLATE 'utf8mb4_general_ci' NULL AFTER `name`; 3 | -------------------------------------------------------------------------------- /migrations/structures/2020-10-19-173611-featured-addon.sql: -------------------------------------------------------------------------------- 1 | ALTER TABLE `addon` ADD `featured_at` DATE DEFAULT NULL; 2 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "componette", 3 | "version": "1.0.0", 4 | "repository": { 5 | "type": "git", 6 | "url": "git@github.com:contributte/componette-site.git" 7 | }, 8 | "licence": "MIT", 9 | "dependencies": { 10 | "alpinejs": "^3.12.2", 11 | "nette-forms": "^3.3.0", 12 | "svgxuse": "^1.2.6" 13 | }, 14 | "devDependencies": { 15 | "@tailwindcss/forms": "^0.5.3", 16 | "@types/google.analytics": "0.0.42", 17 | "@types/node": "^18", 18 | "autoprefixer": "^10.4.12", 19 | "clean-webpack-plugin": "^4.0.0", 20 | "cross-env": "^7.0.2", 21 | "css-loader": "^6.6.0", 22 | "css-minimizer-webpack-plugin": "^4.2.2", 23 | "file-loader": "^6.2.0", 24 | "image-webpack-loader": "^8.1.0", 25 | "mini-css-extract-plugin": "^2.7.6", 26 | "postcss": "^8.4.31", 27 | "postcss-loader": "^7.0.1", 28 | "prettier": "^2.5.1", 29 | "tailwindcss": "^3.2.1", 30 | "tailwindcss-gradients": "^3.0.0", 31 | "webpack-manifest-plugin": "^5.0.0", 32 | "terser-webpack-plugin": "^5.3.1", 33 | "ts-loader": "^9.2.6", 34 | "ts-node": "^10.5.0", 35 | "typescript": "^4.5.5", 36 | "webpack": "^5.85.0", 37 | "webpack-cli": "^4.9.2", 38 | "webpack-dev-server": "^4.7.4", 39 | "webpack-merge": "^5.8.0" 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /phpstan.neon: -------------------------------------------------------------------------------- 1 | parameters: 2 | level: 9 3 | phpVersion: 80000 4 | 5 | paths: 6 | - app 7 | 8 | excludePaths: 9 | - */tests/tmp/* 10 | 11 | fileExtensions: 12 | - php 13 | - phpt 14 | 15 | ignoreErrors: 16 | # Nextras 17 | - '#^Method App\\Model\\Database\\ORM\\[a-zA-Z]+\\[a-zA-Z]+::[a-zA-Z]+\(\) should return App\\Model\\Database\\ORM\\[a-zA-Z]+\\[a-zA-Z]+\|null but returns Nextras\\Orm\\Entity\\IEntity\|null\.$#' 18 | - '#^Method App\\Model\\Facade\\[a-zA-Z]+::[a-zA-Z]+\(\) should return App\\Model\\Database\\ORM\\[a-zA-Z]+\\[a-zA-Z]+\|null but returns Nextras\\Orm\\Entity\\IEntity\|null\.$#' 19 | 20 | # Nette Presenters 21 | - '#Access to an undefined property Nette\\ComponentModel\\IComponent::\$controlPrototype.#' 22 | 23 | includes: 24 | - phpstan-baseline.neon 25 | # Extensions 26 | - vendor/phpstan/phpstan-nette/extension.neon 27 | - vendor/phpstan/phpstan-nette/rules.neon 28 | #- vendor/nextras/orm-phpstan/extension.neon 29 | -------------------------------------------------------------------------------- /postcss.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | plugins: [ 3 | require('tailwindcss'), 4 | require('postcss-nested'), 5 | require('autoprefixer'), 6 | ], 7 | }; 8 | -------------------------------------------------------------------------------- /prettier.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | semi: true, 3 | singleQuote: true, 4 | tabWidth: 2, 5 | useTabs: false, 6 | }; 7 | -------------------------------------------------------------------------------- /ruleset.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 12 | 13 | 14 | 15 | 16 | 17 | 22 | 23 | 24 | 25 | 26 | /app/model/Database/ORM 27 | 28 | 29 | 30 | /app/model/Database/ORM 31 | 32 | 33 | 34 | /tests/tmp 35 | 36 | -------------------------------------------------------------------------------- /temp/.gitignore: -------------------------------------------------------------------------------- 1 | * 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /tests/.gitignore: -------------------------------------------------------------------------------- 1 | # Folders 2 | output 3 | /tmp 4 | 5 | # Logs 6 | /*.log 7 | -------------------------------------------------------------------------------- /tests/bootstrap.container.php: -------------------------------------------------------------------------------- 1 | setTempDirectory(TMP_DIR); 11 | 12 | $configurator->createRobotLoader() 13 | ->addDirectory(APP_DIR) 14 | ->register(); 15 | 16 | $configurator->addConfig(APP_DIR . '/config/config.neon'); 17 | 18 | // Add test configs and test environments 19 | $configurator->addConfig(APP_DIR . '/config/config.test.neon'); 20 | 21 | // Setup debugMode of course! 22 | $configurator->setDebugMode(true); 23 | 24 | // Override to original wwwDir 25 | $configurator->addParameters([ 26 | 'wwwDir' => WWW_DIR, 27 | ]); 28 | 29 | // Create test container 30 | return $configurator->createContainer(); 31 | -------------------------------------------------------------------------------- /tests/bootstrap.php: -------------------------------------------------------------------------------- 1 | getByType(ITemplateFactory::class); 21 | Assert::type(TemplateFactory::class, $templateFactory); 22 | 23 | /** @var Template $template */ 24 | $template = $templateFactory->createTemplate(); 25 | 26 | $finder = Finder::findFiles('*.latte')->from(APP_DIR); 27 | foreach ($finder as $file) { 28 | $template->getLatte()->warmupCache((string) $file); 29 | } 30 | } catch (Throwable $e) { 31 | Assert::fail(sprintf('Template compilation failed (%s)', $e->getMessage())); 32 | } 33 | }); 34 | -------------------------------------------------------------------------------- /tests/cases/Integration/Nette/Container/Builder.development.phpt: -------------------------------------------------------------------------------- 1 | setTempDirectory(TEMP_DIR); 19 | 20 | $configurator->createRobotLoader() 21 | ->addDirectory(APP_DIR) 22 | ->register(); 23 | 24 | $configurator->addConfig(APP_DIR . '/config/config.neon'); 25 | $configurator->addConfig(APP_DIR . '/config/config.test.neon'); 26 | 27 | try { 28 | $configurator->setDebugMode(true); 29 | $container = $configurator->createContainer(); 30 | Assert::type(Container::class, $container); 31 | } catch (Throwable $e) { 32 | Assert::fail(sprintf('Building development container failed. Exception: %s.', $e->getMessage())); 33 | } 34 | }); 35 | -------------------------------------------------------------------------------- /tests/cases/Integration/Nette/Container/Builder.production.phpt: -------------------------------------------------------------------------------- 1 | setTempDirectory(TEMP_DIR); 19 | 20 | $configurator->createRobotLoader() 21 | ->addDirectory(APP_DIR) 22 | ->register(); 23 | 24 | $configurator->addConfig(APP_DIR . '/config/config.neon'); 25 | $configurator->addConfig(APP_DIR . '/config/config.test.neon'); 26 | 27 | try { 28 | $configurator->setDebugMode(false); 29 | $container = $configurator->createContainer(); 30 | Assert::type(Container::class, $container); 31 | } catch (Throwable $e) { 32 | Assert::fail(sprintf('Building production container failed. Exception: %s.', $e->getMessage())); 33 | } 34 | }); 35 | -------------------------------------------------------------------------------- /tests/cases/Unit/Model/Templating/Filters/Filters.timeAgo.phpt: -------------------------------------------------------------------------------- 1 | ArrayHash 10 | test(function (): void { 11 | $array = [ 12 | 'foo' => [ 13 | 'bar' => 1, 14 | ], 15 | ]; 16 | 17 | $ah = ArrayHash::from($array, true); 18 | Assert::type(ArrayHash::class, $ah->foo); 19 | Assert::false(is_array($ah->foo)); 20 | }); 21 | 22 | // ArrayHash => array 23 | test(function (): void { 24 | $array = [ 25 | 'foo' => [ 26 | 'bar' => 1, 27 | ], 28 | ]; 29 | 30 | $ah = ArrayHash::from($array, true); 31 | $r = (array) $ah; 32 | 33 | Assert::true(is_array($r)); 34 | Assert::type(ArrayHash::class, $r['foo']); 35 | 36 | $r = Arrays::ensure($ah); 37 | 38 | Assert::true(is_array($r)); 39 | Assert::true(is_array($r['foo'])); 40 | }); 41 | -------------------------------------------------------------------------------- /tests/php-unix.ini: -------------------------------------------------------------------------------- 1 | [PHP] 2 | extension=tokenizer.so 3 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compileOnSave": false, 3 | "compilerOptions": { 4 | "allowJs": true, 5 | "esModuleInterop": true, 6 | "jsx": "react", 7 | "lib": ["dom", "dom.iterable", "esnext"], 8 | "module": "esnext", 9 | "moduleResolution": "node", 10 | "noEmit": false, 11 | "noUnusedLocals": true, 12 | "noUnusedParameters": false, 13 | "preserveConstEnums": true, 14 | "removeComments": false, 15 | "resolveJsonModule": true, 16 | "skipLibCheck": true, 17 | "sourceMap": true, 18 | "strict": true, 19 | "strictPropertyInitialization": false, 20 | "target": "esnext", 21 | "typeRoots": ["./node_modules/@types", "./app/assets/types"] 22 | }, 23 | "exclude": ["/**/node_modules", "/**/vendor"], 24 | "include": ["app/assets/**/*", "webpack/**/*"] 25 | } 26 | -------------------------------------------------------------------------------- /webpack/loaders/images.ts: -------------------------------------------------------------------------------- 1 | import { RuleSetRule } from 'webpack'; 2 | 3 | export default (): RuleSetRule[] => [ 4 | { 5 | test: /\.(jpe?g|png|gif|svg)$/i, 6 | use: [ 7 | { 8 | loader: 'file-loader', 9 | options: { 10 | esModule: false, 11 | name: '[name].[fullhash].[ext]', 12 | }, 13 | }, 14 | { 15 | loader: 'image-webpack-loader', 16 | options: { 17 | mozjpeg: { 18 | progressive: true, 19 | quality: 65, 20 | }, 21 | optipng: { 22 | enabled: true, 23 | }, 24 | pngquant: { 25 | quality: [0.65, 0.9], 26 | speed: 4, 27 | }, 28 | gifsicle: { 29 | interlaced: false, 30 | }, 31 | }, 32 | }, 33 | ], 34 | }, 35 | ]; 36 | -------------------------------------------------------------------------------- /webpack/loaders/index.ts: -------------------------------------------------------------------------------- 1 | import { RuleSetRule } from 'webpack'; 2 | 3 | import images from './images'; 4 | import scripts from './scripts'; 5 | import styles from './styles'; 6 | 7 | const makeLoaders = (): RuleSetRule[] => [ 8 | ...images(), 9 | ...scripts(), 10 | ...styles(), 11 | ]; 12 | 13 | export default makeLoaders; 14 | -------------------------------------------------------------------------------- /webpack/loaders/scripts.ts: -------------------------------------------------------------------------------- 1 | import { RuleSetRule } from 'webpack'; 2 | 3 | export default (): RuleSetRule[] => [ 4 | { test: /\.ts$/, exclude: /node_modules/, loader: 'ts-loader' }, 5 | ]; 6 | -------------------------------------------------------------------------------- /webpack/loaders/styles.ts: -------------------------------------------------------------------------------- 1 | import MiniCssExtractPlugin from 'mini-css-extract-plugin'; 2 | import { RuleSetRule } from 'webpack'; 3 | 4 | export default (): RuleSetRule[] => [ 5 | { 6 | test: /\.css$/, 7 | use: [ 8 | { 9 | loader: MiniCssExtractPlugin.loader, 10 | }, 11 | 'css-loader', 12 | 'postcss-loader', 13 | ], 14 | }, 15 | ]; 16 | -------------------------------------------------------------------------------- /webpack/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "compilerOptions": { 4 | "module": "CommonJS", 5 | "target": "es5" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /webpack/utils.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from 'path'; 2 | 3 | import { Entry } from 'webpack'; 4 | 5 | export const DEV = process.env.NODE_ENV !== 'production'; 6 | export const ROOT = resolve(__dirname, '..'); 7 | 8 | export const entry: Entry = { 9 | front: resolve('app', 'assets', 'front.ts'), 10 | }; 11 | -------------------------------------------------------------------------------- /webpack/webpack.config.ts: -------------------------------------------------------------------------------- 1 | import { WebpackManifestPlugin } from 'webpack-manifest-plugin'; 2 | import merge from 'webpack-merge'; 3 | import MiniCssExtractPlugin from 'mini-css-extract-plugin'; 4 | import { Configuration, ProvidePlugin } from 'webpack'; 5 | 6 | import { DEV, entry } from './utils'; 7 | import makeLoaders from './loaders'; 8 | 9 | const baseConfig: Configuration = { 10 | entry: entry, 11 | module: { 12 | rules: makeLoaders(), 13 | }, 14 | target: 'web', 15 | plugins: [ 16 | new MiniCssExtractPlugin({ 17 | filename: `[name]${DEV ? '' : '.[fullhash]'}.css`, 18 | chunkFilename: `[id]${DEV ? '' : '.[fullhash]'}.css`, 19 | }), 20 | new WebpackManifestPlugin({ 21 | fileName: 'manifest.json', 22 | publicPath: '', 23 | }), 24 | new ProvidePlugin({ 25 | $: 'jquery', 26 | jQuery: 'jquery', 27 | }), 28 | ], 29 | resolve: { 30 | extensions: ['.ts', '.js'], 31 | }, 32 | }; 33 | 34 | const makeConfig = (config: Configuration): Configuration => 35 | merge(baseConfig, config); 36 | 37 | export default makeConfig; 38 | -------------------------------------------------------------------------------- /webpack/webpack.dev.ts: -------------------------------------------------------------------------------- 1 | import 'webpack-dev-server'; 2 | 3 | import makeConfig from './webpack.config'; 4 | 5 | export default makeConfig({ 6 | mode: 'development', 7 | devtool: false, 8 | devServer: { 9 | port: 9006, 10 | host: 'localhost', 11 | hot: true, 12 | headers: { 13 | 'Access-Control-Allow-Headers': '*', 14 | 'Access-Control-Allow-Origin': '*', 15 | }, 16 | }, 17 | output: { 18 | filename: '[name].[fullhash].js', 19 | publicPath: 'http://localhost:9006/', 20 | }, 21 | }); 22 | -------------------------------------------------------------------------------- /webpack/webpack.prod.ts: -------------------------------------------------------------------------------- 1 | import CssMinimizerPlugin from 'css-minimizer-webpack-plugin'; 2 | import { resolve } from 'path'; 3 | import TerserPlugin from 'terser-webpack-plugin'; 4 | import { CleanWebpackPlugin } from 'clean-webpack-plugin'; 5 | 6 | import makeConfig from './webpack.config'; 7 | import { ROOT } from './utils'; 8 | 9 | export default makeConfig({ 10 | mode: 'production', 11 | devtool: 'source-map', 12 | output: { 13 | filename: '[name].[fullhash].js', 14 | path: resolve(ROOT, 'www/dist'), 15 | }, 16 | optimization: { 17 | minimizer: [ 18 | new CssMinimizerPlugin({ 19 | minimizerOptions: { 20 | preset: ['default', { discardComments: { removeAll: true } }], 21 | }, 22 | }), 23 | new TerserPlugin({ 24 | extractComments: false, 25 | terserOptions: { 26 | mangle: true, 27 | output: { 28 | comments: false, 29 | }, 30 | }, 31 | }), 32 | ], 33 | }, 34 | plugins: [new CleanWebpackPlugin()], 35 | stats: 'minimal', 36 | }); 37 | -------------------------------------------------------------------------------- /www/.htaccess: -------------------------------------------------------------------------------- 1 | # Apache configuration file (see httpd.apache.org/docs/current/mod/quickreference.html) 2 | 3 | # disable directory listing 4 | 5 | Options -Indexes 6 | 7 | 8 | # enable cool URL 9 | 10 | RewriteEngine On 11 | # RewriteBase / 12 | 13 | # prevents files starting with dot to be viewed by browser 14 | RewriteRule /\.|^\. - [F] 15 | 16 | # front controller 17 | RewriteCond %{REQUEST_FILENAME} !-f 18 | RewriteCond %{REQUEST_FILENAME} !-d 19 | RewriteRule !\.(pdf|js|ico|css|rar|zip|tar\.gz|map)$ index.php [L] 20 | 21 | 22 | # enable gzip compression 23 | 24 | 25 | AddOutputFilterByType DEFLATE text/html text/plain text/xml text/css application/javascript application/json application/xml image/svg+xml 26 | 27 | 28 | -------------------------------------------------------------------------------- /www/.maintenance.php: -------------------------------------------------------------------------------- 1 | 7 | 8 | 9 | 10 | 11 | 12 | 17 | 18 | Site is temporarily down for maintenance 19 | 20 |

We're Sorry

21 | 22 |

The site is temporarily down for maintenance. Please try again in a few minutes.

23 | 24 | getByType(Nette\Application\Application::class)->run(); 9 | -------------------------------------------------------------------------------- /www/robots.txt: -------------------------------------------------------------------------------- 1 | # Componette 2 | Sitemap: https://componette.org/sitemap.xml 3 | -------------------------------------------------------------------------------- /www/security.txt: -------------------------------------------------------------------------------- 1 | # Welcome cyber security guys! 2 | 3 | # Email 4 | Contact: https://f3l1x.io 5 | 6 | # Twitter 7 | Contact: https://twitter.com/xf3l1x 8 | --------------------------------------------------------------------------------