├── .env ├── .gitattributes ├── .gitignore ├── README.md ├── docker-compose.override.blackfire.yml ├── docker-compose.override.xhprof.yml ├── docker-compose.yml ├── docker ├── logs │ ├── nginx │ │ └── .gitkeep │ └── php │ │ └── .gitkeep ├── mariadb │ └── conf │ │ └── custom.cnf ├── modx │ ├── storage │ │ ├── backup │ │ │ └── .gitkeep │ │ └── cache │ │ │ └── .gitkeep │ └── tools │ │ └── configurator │ │ ├── composer.json │ │ ├── config.inc.php │ │ ├── example.config.inc.php │ │ ├── run.php │ │ ├── src │ │ ├── Runner │ │ │ ├── Runner.php │ │ │ └── RunnerInterface.php │ │ ├── Tasks │ │ │ ├── GrantAccessUserTask.php │ │ │ ├── InstallPackagesTask.php │ │ │ ├── MiniShop2Task.php │ │ │ ├── SetOptionsTask.php │ │ │ ├── Task.php │ │ │ ├── TaskInterface.php │ │ │ └── TransportProvidersTask.php │ │ ├── Traits │ │ │ ├── DocumentTrait.php │ │ │ ├── ElementsTrait.php │ │ │ ├── InitializeTrait.php │ │ │ ├── OptionTrait.php │ │ │ ├── PropertiesTrait.php │ │ │ ├── SecurityTrait.php │ │ │ └── TransportProviderTrait.php │ │ └── Utils │ │ │ └── Logger.php │ │ └── storage │ │ └── ms2 │ │ ├── demo │ │ ├── categories.csv │ │ ├── products.csv │ │ ├── products │ │ │ ├── 1-2-thickbox.jpg │ │ │ ├── 10-21-thickbox.jpg │ │ │ ├── 10-22-thickbox.jpg │ │ │ ├── 100-181-thickbox.jpg │ │ │ ├── 101-182-thickbox.jpg │ │ │ ├── 101-183-thickbox.jpg │ │ │ ├── 101-184-thickbox.jpg │ │ │ ├── 101-185-thickbox.jpg │ │ │ ├── 102-186-thickbox.jpg │ │ │ ├── 103-187-thickbox.jpg │ │ │ ├── 104-188-thickbox.jpg │ │ │ ├── 105-189-thickbox.jpg │ │ │ ├── 106-190-thickbox.jpg │ │ │ ├── 106-191-thickbox.jpg │ │ │ ├── 106-192-thickbox.jpg │ │ │ ├── 106-193-thickbox.jpg │ │ │ ├── 106-195-thickbox.jpg │ │ │ ├── 107-196-thickbox.jpg │ │ │ ├── 107-197-thickbox.jpg │ │ │ ├── 108-198-thickbox.jpg │ │ │ ├── 108-199-thickbox.jpg │ │ │ ├── 108-200-thickbox.jpg │ │ │ ├── 109-201-thickbox.jpg │ │ │ ├── 11-23-thickbox.jpg │ │ │ ├── 11-24-thickbox.jpg │ │ │ ├── 110-202-thickbox.jpg │ │ │ ├── 110-203-thickbox.jpg │ │ │ ├── 111-204-thickbox.jpg │ │ │ ├── 112-205-thickbox.jpg │ │ │ ├── 113-206-thickbox.jpg │ │ │ ├── 114-207-thickbox.jpg │ │ │ ├── 115-208-thickbox.jpg │ │ │ ├── 115-209-thickbox.jpg │ │ │ ├── 116-210-thickbox.jpg │ │ │ ├── 117-211-thickbox.jpg │ │ │ ├── 118-212-thickbox.jpg │ │ │ ├── 119-213-thickbox.jpg │ │ │ ├── 12-25-thickbox.jpg │ │ │ ├── 120-214-thickbox.jpg │ │ │ ├── 121-215-thickbox.jpg │ │ │ ├── 122-216-thickbox.jpg │ │ │ ├── 123-217-thickbox.jpg │ │ │ ├── 124-218-thickbox.jpg │ │ │ ├── 124-219-thickbox.jpg │ │ │ ├── 125-220-thickbox.jpg │ │ │ ├── 126-221-thickbox.jpg │ │ │ ├── 127-222-thickbox.jpg │ │ │ ├── 127-223-thickbox.jpg │ │ │ ├── 127-224-thickbox.jpg │ │ │ ├── 128-225-thickbox.jpg │ │ │ ├── 128-226-thickbox.jpg │ │ │ ├── 129-227-thickbox.jpg │ │ │ ├── 13-26-thickbox.jpg │ │ │ ├── 130-228-thickbox.jpg │ │ │ ├── 131-229-thickbox.jpg │ │ │ ├── 132-230-thickbox.jpg │ │ │ ├── 133-231-thickbox.jpg │ │ │ ├── 134-232-thickbox.jpg │ │ │ ├── 134-233-thickbox.jpg │ │ │ ├── 134-234-thickbox.jpg │ │ │ ├── 135-235-thickbox.jpg │ │ │ ├── 136-236-thickbox.jpg │ │ │ ├── 136-237-thickbox.jpg │ │ │ ├── 136-238-thickbox.jpg │ │ │ ├── 136-239-thickbox.jpg │ │ │ ├── 136-240-thickbox.jpg │ │ │ ├── 137-242-thickbox.jpg │ │ │ ├── 137-243-thickbox.jpg │ │ │ ├── 137-244-thickbox.jpg │ │ │ ├── 137-245-thickbox.jpg │ │ │ ├── 137-246-thickbox.jpg │ │ │ ├── 138-247-thickbox.jpg │ │ │ ├── 138-248-thickbox.jpg │ │ │ ├── 138-249-thickbox.jpg │ │ │ ├── 138-250-thickbox.jpg │ │ │ ├── 138-251-thickbox.jpg │ │ │ ├── 139-252-thickbox.jpg │ │ │ ├── 139-253-thickbox.jpg │ │ │ ├── 139-254-thickbox.jpg │ │ │ ├── 14-27-thickbox.jpg │ │ │ ├── 143-260-thickbox.jpg │ │ │ ├── 143-261-thickbox.jpg │ │ │ ├── 143-262-thickbox.jpg │ │ │ ├── 143-263-thickbox.jpg │ │ │ ├── 144-264-thickbox.jpg │ │ │ ├── 144-265-thickbox.jpg │ │ │ ├── 144-266-thickbox.jpg │ │ │ ├── 144-267-thickbox.jpg │ │ │ ├── 145-268-thickbox.jpg │ │ │ ├── 145-269-thickbox.jpg │ │ │ ├── 145-270-thickbox.jpg │ │ │ ├── 145-271-thickbox.jpg │ │ │ ├── 146-272-thickbox.jpg │ │ │ ├── 146-273-thickbox.jpg │ │ │ ├── 146-274-thickbox.jpg │ │ │ ├── 146-275-thickbox.jpg │ │ │ ├── 147-276-thickbox.jpg │ │ │ ├── 147-277-thickbox.jpg │ │ │ ├── 147-278-thickbox.jpg │ │ │ ├── 148-279-thickbox.jpg │ │ │ ├── 148-280-thickbox.jpg │ │ │ ├── 148-281-thickbox.jpg │ │ │ ├── 148-282-thickbox.jpg │ │ │ ├── 148-283-thickbox.jpg │ │ │ ├── 149-284-thickbox.jpg │ │ │ ├── 149-285-thickbox.jpg │ │ │ ├── 149-286-thickbox.jpg │ │ │ ├── 15-28-thickbox.jpg │ │ │ ├── 15-29-thickbox.jpg │ │ │ ├── 150-287-thickbox.jpg │ │ │ ├── 151-288-thickbox.jpg │ │ │ ├── 152-289-thickbox.jpg │ │ │ ├── 153-290-thickbox.jpg │ │ │ ├── 16-30-thickbox.jpg │ │ │ ├── 17-31-thickbox.jpg │ │ │ ├── 17-32-thickbox.jpg │ │ │ ├── 17-33-thickbox.jpg │ │ │ ├── 17-34-thickbox.jpg │ │ │ ├── 17-35-thickbox.jpg │ │ │ ├── 18-37-thickbox.jpg │ │ │ ├── 19-40-thickbox.jpg │ │ │ ├── 19-41-thickbox.jpg │ │ │ ├── 2-3-thickbox.jpg │ │ │ ├── 2-4-thickbox.jpg │ │ │ ├── 20-38-thickbox.jpg │ │ │ ├── 20-39-thickbox.jpg │ │ │ ├── 21-42-thickbox.jpg │ │ │ ├── 21-43-thickbox.jpg │ │ │ ├── 21-44-thickbox.jpg │ │ │ ├── 21-45-thickbox.jpg │ │ │ ├── 22-46-thickbox.jpg │ │ │ ├── 23-47-thickbox.jpg │ │ │ ├── 23-48-thickbox.jpg │ │ │ ├── 23-49-thickbox.jpg │ │ │ ├── 24-50-thickbox.jpg │ │ │ ├── 24-51-thickbox.jpg │ │ │ ├── 24-52-thickbox.jpg │ │ │ ├── 24-53-thickbox.jpg │ │ │ ├── 24-54-thickbox.jpg │ │ │ ├── 25-56-thickbox.jpg │ │ │ ├── 25-57-thickbox.jpg │ │ │ ├── 26-58-thickbox.jpg │ │ │ ├── 27-59-thickbox.jpg │ │ │ ├── 27-60-thickbox.jpg │ │ │ ├── 27-61-thickbox.jpg │ │ │ ├── 27-62-thickbox.jpg │ │ │ ├── 27-63-thickbox.jpg │ │ │ ├── 28-65-thickbox.jpg │ │ │ ├── 28-66-thickbox.jpg │ │ │ ├── 29-67-thickbox.jpg │ │ │ ├── 29-68-thickbox.jpg │ │ │ ├── 29-69-thickbox.jpg │ │ │ ├── 3-5-thickbox.jpg │ │ │ ├── 30-70-thickbox.jpg │ │ │ ├── 30-71-thickbox.jpg │ │ │ ├── 30-72-thickbox.jpg │ │ │ ├── 31-73-thickbox.jpg │ │ │ ├── 32-74-thickbox.jpg │ │ │ ├── 32-75-thickbox.jpg │ │ │ ├── 32-76-thickbox.jpg │ │ │ ├── 32-77-thickbox.jpg │ │ │ ├── 33-78-thickbox.jpg │ │ │ ├── 34-79-thickbox.jpg │ │ │ ├── 35-80-thickbox.jpg │ │ │ ├── 36-81-thickbox.jpg │ │ │ ├── 37-82-thickbox.jpg │ │ │ ├── 37-83-thickbox.jpg │ │ │ ├── 37-84-thickbox.jpg │ │ │ ├── 37-85-thickbox.jpg │ │ │ ├── 38-86-thickbox.jpg │ │ │ ├── 39-87-thickbox.jpg │ │ │ ├── 4-10-thickbox.jpg │ │ │ ├── 4-6-thickbox.jpg │ │ │ ├── 4-7-thickbox.jpg │ │ │ ├── 4-8-thickbox.jpg │ │ │ ├── 4-9-thickbox.jpg │ │ │ ├── 40-88-thickbox.jpg │ │ │ ├── 40-89-thickbox.jpg │ │ │ ├── 40-90-thickbox.jpg │ │ │ ├── 40-91-thickbox.jpg │ │ │ ├── 41-92-thickbox.jpg │ │ │ ├── 42-93-thickbox.jpg │ │ │ ├── 43-94-thickbox.jpg │ │ │ ├── 44-95-thickbox.jpg │ │ │ ├── 45-96-thickbox.jpg │ │ │ ├── 46-97-thickbox.jpg │ │ │ ├── 47-98-thickbox.jpg │ │ │ ├── 48-100-thickbox.jpg │ │ │ ├── 48-99-thickbox.jpg │ │ │ ├── 49-101-thickbox.jpg │ │ │ ├── 5-12-thickbox.jpg │ │ │ ├── 50-102-thickbox.jpg │ │ │ ├── 51-103-thickbox.jpg │ │ │ ├── 52-104-thickbox.jpg │ │ │ ├── 53-105-thickbox.jpg │ │ │ ├── 54-106-thickbox.jpg │ │ │ ├── 55-107-thickbox.jpg │ │ │ ├── 56-108-thickbox.jpg │ │ │ ├── 57-109-thickbox.jpg │ │ │ ├── 58-110-thickbox.jpg │ │ │ ├── 6-13-thickbox.jpg │ │ │ ├── 60-111-thickbox.jpg │ │ │ ├── 61-112-thickbox.jpg │ │ │ ├── 62-113-thickbox.jpg │ │ │ ├── 63-114-thickbox.jpg │ │ │ ├── 64-115-thickbox.jpg │ │ │ ├── 65-116-thickbox.jpg │ │ │ ├── 66-117-thickbox.jpg │ │ │ ├── 67-118-thickbox.jpg │ │ │ ├── 67-119-thickbox.jpg │ │ │ ├── 67-120-thickbox.jpg │ │ │ ├── 67-121-thickbox.jpg │ │ │ ├── 68-122-thickbox.jpg │ │ │ ├── 69-123-thickbox.jpg │ │ │ ├── 7-14-thickbox.jpg │ │ │ ├── 7-15-thickbox.jpg │ │ │ ├── 7-16-thickbox.jpg │ │ │ ├── 7-17-thickbox.jpg │ │ │ ├── 70-124-thickbox.jpg │ │ │ ├── 71-125-thickbox.jpg │ │ │ ├── 72-126-thickbox.jpg │ │ │ ├── 72-127-thickbox.jpg │ │ │ ├── 72-128-thickbox.jpg │ │ │ ├── 72-129-thickbox.jpg │ │ │ ├── 73-130-thickbox.jpg │ │ │ ├── 73-131-thickbox.jpg │ │ │ ├── 73-132-thickbox.jpg │ │ │ ├── 73-133-thickbox.jpg │ │ │ ├── 74-134-thickbox.jpg │ │ │ ├── 75-135-thickbox.jpg │ │ │ ├── 76-136-thickbox.jpg │ │ │ ├── 77-137-thickbox.jpg │ │ │ ├── 78-138-thickbox.jpg │ │ │ ├── 78-139-thickbox.jpg │ │ │ ├── 78-140-thickbox.jpg │ │ │ ├── 79-141-thickbox.jpg │ │ │ ├── 8-18-thickbox.jpg │ │ │ ├── 8-19-thickbox.jpg │ │ │ ├── 80-142-thickbox.jpg │ │ │ ├── 80-143-thickbox.jpg │ │ │ ├── 81-144-thickbox.jpg │ │ │ ├── 82-145-thickbox.jpg │ │ │ ├── 82-146-thickbox.jpg │ │ │ ├── 83-147-thickbox.jpg │ │ │ ├── 84-148-thickbox.jpg │ │ │ ├── 84-149-thickbox.jpg │ │ │ ├── 85-150-thickbox.jpg │ │ │ ├── 86-151-thickbox.jpg │ │ │ ├── 87-152-thickbox.jpg │ │ │ ├── 88-153-thickbox.jpg │ │ │ ├── 89-154-thickbox.jpg │ │ │ ├── 9-20-thickbox.jpg │ │ │ ├── 90-155-thickbox.jpg │ │ │ ├── 91-156-thickbox.jpg │ │ │ ├── 91-157-thickbox.jpg │ │ │ ├── 91-158-thickbox.jpg │ │ │ ├── 91-159-thickbox.jpg │ │ │ ├── 91-160-thickbox.jpg │ │ │ ├── 92-162-thickbox.jpg │ │ │ ├── 92-163-thickbox.jpg │ │ │ ├── 93-164-thickbox.jpg │ │ │ ├── 94-165-thickbox.jpg │ │ │ ├── 94-166-thickbox.jpg │ │ │ ├── 94-167-thickbox.jpg │ │ │ ├── 94-169-thickbox.jpg │ │ │ ├── 94-170-thickbox.jpg │ │ │ ├── 95-172-thickbox.jpg │ │ │ ├── 96-173-thickbox.jpg │ │ │ ├── 97-174-thickbox.jpg │ │ │ ├── 97-175-thickbox.jpg │ │ │ ├── 97-177-thickbox.jpg │ │ │ ├── 97-178-thickbox.jpg │ │ │ ├── 98-179-thickbox.jpg │ │ │ └── 99-180-thickbox.jpg │ │ ├── vendors.csv │ │ └── vendors │ │ │ ├── asus.jpg │ │ │ ├── bosch.jpg │ │ │ ├── canon.jpg │ │ │ ├── dlink.jpg │ │ │ ├── motorola.jpg │ │ │ ├── nikon.jpg │ │ │ ├── panasonic.jpg │ │ │ ├── philips.jpg │ │ │ └── samsung.jpg │ │ ├── pages │ │ ├── cart.tpl │ │ └── category.tpl │ │ └── templates │ │ ├── cart.tpl │ │ ├── category.tpl │ │ └── product.tpl ├── nginx │ ├── default.conf.template │ └── ssl │ │ └── .gitkeep ├── php │ ├── Dockerfile │ ├── conf │ │ ├── opcache.ini │ │ ├── php.ini │ │ ├── xdebug.ini │ │ └── xhprof.ini │ ├── sh │ │ ├── modx-clear-db.sh │ │ ├── modx-clear-site.sh │ │ ├── modx-configure.sh │ │ ├── modx-docker-start.sh │ │ ├── modx-download.sh │ │ ├── modx-export.sh │ │ ├── modx-generate-ssl.sh │ │ ├── modx-import.sh │ │ ├── modx-install.sh │ │ ├── modx-uninstall.sh │ │ └── modx-upgrade.sh │ └── xhprof │ │ ├── composer.json │ │ └── handler.php ├── volume │ └── mariadb │ │ └── .gitkeep └── xhgui │ ├── Dockerfile │ ├── apache.conf │ ├── config.php │ ├── mongo.init.d │ └── xhgui.js │ └── templates │ └── runs │ └── view.twig ├── docs ├── images │ ├── architecture-dark.svg │ ├── architecture-light.svg │ └── video-cover.jpg └── readme-ru.md └── www └── .gitkeep /.env: -------------------------------------------------------------------------------- 1 | PHP_VERSION=7.4 2 | MODX_VERSION=2.8.8-pl 3 | 4 | MODX_INSTALL_ENABLE=1 5 | MODX_USE_CACHE_SOURCE=1 6 | 7 | MODX_CONFIGURE_ENABLE=0 8 | MODX_CONFIGURE_DEV_MODE=0 9 | 10 | MODX_DB_SERVER=mariadb 11 | MODX_DB_NAME=modx 12 | MODX_DB_USER=modx 13 | MODX_DB_PASSWORD=modx 14 | MODX_DB_CONNECTION_CHARSET=utf8mb4 15 | MODX_DB_CHARSET=utf8mb4 16 | MODX_DB_CHARSET_COLLATION=utf8mb4_general_ci 17 | MODX_TABLE_PREFIX=random:8 18 | MODX_HTTPS_PORT=443 19 | MODX_HTTP_HOST=localhost 20 | MODX_LANGUAGE=en 21 | MODX_CMS_ADMIN=admin 22 | MODX_CMS_PASS=admin 23 | MODX_CMS_EMAIL=email@example.local 24 | MODX_CORE_PATH=/var/www/html/core/ 25 | MODX_BASE_PATH=/var/www/html/ 26 | MODX_BASE_URL=/ 27 | MODX_CONNECTORS_PATH=/var/www/html/connectors/ 28 | MODX_CONNECTORS_URL=/connectors/ 29 | MODX_MANAGER_PATH=/var/www/html/manager/ 30 | MODX_MANAGER_URL=/manager/ 31 | MODX_REMOVE_SETUP_DIRECTORY=1 32 | 33 | MODX_IMPORT_DB=1 34 | MODX_IMPORT_SITE=1 35 | 36 | MODX_EXPORT_DB=1 37 | MODX_EXPORT_SITE=1 38 | MODX_EXPORT_OVERWRITE_CONFIG=0 39 | 40 | MODX_EXPORT_DB_SERVER= 41 | MODX_EXPORT_DB_NAME= 42 | MODX_EXPORT_DB_USER= 43 | MODX_EXPORT_DB_PASSWORD= 44 | MODX_EXPORT_HTTP_HOST= 45 | MODX_EXPORT_CORE_PATH= 46 | MODX_EXPORT_BASE_PATH= 47 | MODX_EXPORT_BASE_URL= 48 | MODX_EXPORT_ASSETS_PATH= 49 | MODX_EXPORT_ASSETS_URL= 50 | MODX_EXPORT_CONNECTORS_PATH= 51 | MODX_EXPORT_CONNECTORS_URL= 52 | MODX_EXPORT_MANAGER_PATH= 53 | MODX_EXPORT_MANAGER_URL= 54 | 55 | XDEBUG_ENABLE=0 56 | XHPROF_ENABLE=0 57 | 58 | XHGUI_PORT=8181 59 | XHGUI_MONGO_URI=mongodb://mongo:27017/xhprof 60 | XHGUI_MONGO_HOSTNAME=mongo 61 | XHGUI_MONGO_DATABASE=xhprof 62 | 63 | BLACKFIRE_ENABLE=0 64 | BLACKFIRE_CLIENT_ID= 65 | BLACKFIRE_CLIENT_TOKEN= 66 | BLACKFIRE_SERVER_ID= 67 | BLACKFIRE_SERVER_TOKEN= 68 | 69 | SSH_ENABLE=1 70 | SSH_LOGIN=dev 71 | SSH_PASSWORD=dev 72 | 73 | SSL_PATH=/etc/nginx/ssl 74 | SSL_GENERATE=1 75 | 76 | NGINX_PORT=80 77 | 78 | SMTP_PORT=1025 79 | SMTP_HOST=mailhog 80 | 81 | MAILHOG_PORT=8025 82 | 83 | MARIADB_PORT=3306 84 | PHPMYADMIN_PORT=8080 85 | 86 | MYSQL_ROOT_PASSWORD=root 87 | 88 | ROOT_PATH=/var/www/html 89 | SERVER_NAME=localhost 90 | LOCALE=en_US.UTF-8 -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | *.sh text eol=lf -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .idea/ 2 | *.key 3 | *.pem 4 | *.crt 5 | *.csr 6 | *.logxw -------------------------------------------------------------------------------- /docker-compose.override.blackfire.yml: -------------------------------------------------------------------------------- 1 | services: 2 | php: 3 | build: 4 | args: 5 | BLACKFIRE_ENABLE: ${BLACKFIRE_ENABLE} 6 | 7 | blackfire: 8 | image: blackfire/blackfire:2 9 | ports: ["8307"] 10 | env_file: 11 | - .env -------------------------------------------------------------------------------- /docker-compose.override.xhprof.yml: -------------------------------------------------------------------------------- 1 | services: 2 | php: 3 | build: 4 | args: 5 | XHPROF_ENABLE: ${XHPROF_ENABLE} 6 | mongo: 7 | image: mongo:latest 8 | container_name: xhgui-mongo 9 | volumes: 10 | - mongo_data:/data/db 11 | - ./docker/xhgui/mongo.init.d:/docker-entrypoint-initdb.d 12 | ports: 13 | - "27017:27017" 14 | environment: 15 | - MONGO_INITDB_DATABASE=xhprof 16 | command: --storageEngine=wiredTiger 17 | xhgui: 18 | build: 19 | context: ./docker/xhgui 20 | dockerfile: Dockerfile 21 | container_name: xhgui 22 | ports: 23 | - "${XHGUI_PORT}:80" 24 | env_file: 25 | - .env 26 | depends_on: 27 | - mongo 28 | 29 | volumes: 30 | mongo_data: -------------------------------------------------------------------------------- /docker-compose.yml: -------------------------------------------------------------------------------- 1 | services: 2 | nginx: 3 | image: nginx:latest 4 | container_name: nginx 5 | ports: 6 | - "443:443" 7 | - "${NGINX_PORT}:80" 8 | volumes: 9 | - ./www:${ROOT_PATH} 10 | - ./docker/nginx/ssl:${SSL_PATH} 11 | - ./docker/logs/nginx:/var/log/nginx 12 | - ./docker/nginx/default.conf.template:/etc/nginx/templates/default.conf.template 13 | env_file: 14 | - .env 15 | depends_on: 16 | - php 17 | 18 | mariadb: 19 | image: mariadb:latest 20 | container_name: mariadb 21 | volumes: 22 | - ./docker/volume/mariadb:/var/lib/mysql 23 | - ./docker/mariadb/conf/custom.cnf:/etc/mysql/conf.d/custom.cnf:ro 24 | ports: 25 | - "${MARIADB_PORT}:3306" 26 | environment: 27 | MYSQL_DATABASE: ${MODX_DB_NAME} 28 | MYSQL_USER: ${MODX_DB_USER} 29 | MYSQL_PASSWORD: ${MODX_DB_PASSWORD} 30 | env_file: 31 | - .env 32 | 33 | php: 34 | container_name: "php-${PHP_VERSION}" 35 | build: 36 | context: ./docker/php 37 | dockerfile: Dockerfile 38 | args: 39 | LOCALE: ${LOCALE} 40 | SMTP_PORT: ${SMTP_PORT} 41 | SMTP_HOST: ${SMTP_HOST} 42 | ROOT_PATH: ${ROOT_PATH} 43 | PHP_VERSION: ${PHP_VERSION} 44 | XDEBUG_ENABLE: ${XDEBUG_ENABLE} 45 | SSH_ENABLE: ${SSH_ENABLE} 46 | SSH_LOGIN: ${SSH_LOGIN} 47 | SSH_PASSWORD: ${SSH_PASSWORD} 48 | volumes: 49 | - ./www:${ROOT_PATH} 50 | - ./docker/logs/php:/var/log/php-fpm 51 | - ./docker/nginx/ssl:${SSL_PATH} 52 | - ./docker/modx/tools:/usr/local/modx/tools 53 | - ./docker/modx/storage:/usr/local/modx/storage 54 | - ./docker/php/conf/php.ini:/usr/local/etc/php/conf.d/php.ini 55 | - ./docker/php/conf/xdebug.ini:/usr/local/etc/php/conf.d/xdebug.ini 56 | - ./docker/php/conf/xhprof.ini:/usr/local/etc/php/conf.d/xhprof.ini 57 | - ./docker/php/conf/opcache.ini:/usr/local/etc/php/conf.d/opcache.ini 58 | environment: 59 | MODX_RESET: ${MODX_RESET:-0} 60 | MODX_IMPORT: ${MODX_IMPORT:-0} 61 | env_file: 62 | - .env 63 | ports: 64 | - "2222:22" 65 | depends_on: 66 | - mariadb 67 | # restart: unless-stopped 68 | 69 | phpmyadmin: 70 | image: phpmyadmin:latest 71 | container_name: phpmyadmin 72 | environment: 73 | PMA_HOST: mariadb 74 | PMA_PORT: 3306 75 | UPLOAD_LIMIT: ${PHPMYADMIN_UPLOAD_LIMIT:-100M} 76 | ports: 77 | - "${PHPMYADMIN_PORT}:80" 78 | depends_on: 79 | - mariadb 80 | 81 | mailhog: 82 | image: mailhog/mailhog 83 | container_name: mailhog 84 | ports: 85 | - "${MAILHOG_PORT}:8025" 86 | - "${SMTP_PORT}:1025" -------------------------------------------------------------------------------- /docker/logs/nginx/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Prihod/docker-modx/4fa05bd11e002f3e867d46cfa59956c613a33d52/docker/logs/nginx/.gitkeep -------------------------------------------------------------------------------- /docker/logs/php/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Prihod/docker-modx/4fa05bd11e002f3e867d46cfa59956c613a33d52/docker/logs/php/.gitkeep -------------------------------------------------------------------------------- /docker/mariadb/conf/custom.cnf: -------------------------------------------------------------------------------- 1 | [mysqld] 2 | sql_mode="NO_ENGINE_SUBSTITUTION" -------------------------------------------------------------------------------- /docker/modx/storage/backup/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Prihod/docker-modx/4fa05bd11e002f3e867d46cfa59956c613a33d52/docker/modx/storage/backup/.gitkeep -------------------------------------------------------------------------------- /docker/modx/storage/cache/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Prihod/docker-modx/4fa05bd11e002f3e867d46cfa59956c613a33d52/docker/modx/storage/cache/.gitkeep -------------------------------------------------------------------------------- /docker/modx/tools/configurator/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "autoload": { 3 | "psr-4": { 4 | "App\\": "src/" 5 | } 6 | }, 7 | "require": { 8 | "php": "^7.4" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /docker/modx/tools/configurator/config.inc.php: -------------------------------------------------------------------------------- 1 | [ 14 | 'name' => 'modstore.pro', 15 | 'service_url' => 'https://modstore.pro/extras/', 16 | 'username' => '', 17 | 'api_key' => '', 18 | ], 19 | ]; 20 | $config['install_packages'] = [ 21 | 'ace' => [ 22 | 'name' => 'Ace', 23 | 'version' => '', 24 | 'provider' => 'modx.com', 25 | ], 26 | 'controlerrorlog' => [ 27 | 'name' => 'controlErrorLog', 28 | ], 29 | 'console' => [ 30 | 'name' => 'Console', 31 | ], 32 | ]; 33 | 34 | $config['set_options'] = [ 35 | 'friendly_urls' => 1, 36 | 'use_alias_path' => 1, 37 | ]; 38 | 39 | $config['grant_access_user'] = [ 40 | 'manager' => [ 41 | 'users' => [[ 42 | 'username' => 'manager', 43 | 'password' => '', 44 | 'email' => '', 45 | ]], 46 | 'context_key' => 'mgr', 47 | 'group_name' => 'Manager', 48 | 'access_role_name' => '', 49 | 'access_role_authority' => 9, 50 | 'access_policy_name' => '', 51 | 'access_policy_template_name' => '', 52 | 'access_policy_template_override' => 'AdministratorTemplate', 53 | // see more (en) https://gist.github.com/Prihod/09ff88088c67dff4a48290344a46ec9b 54 | // see more (ru) https://gist.github.com/Prihod/ce52b1cb0e12ac9b5f14bc94dfcd03a5 55 | 'permissions' => [ 56 | 'frames' => 1, // To use the MODX Manager UI at all. 57 | 'home' => 1, // To view the Welcome page. 58 | 'menu_user' => 1, // Show the top menu item "User". 59 | 'view_template' => 1, // To view any Templates. 60 | 'class_map' => 1, // To view a list of classes in the Class Map. 61 | 'change_password' => 1, // The user can change their password. 62 | 'change_profile' => 1, // The user can edit their profile. 63 | 'countries' => 1, // View the list of countries. 64 | 'create' => 1, // Ability to "create" new objects. 65 | 'delete_document' => 1, // Delete and move resources. 66 | 'delete_static_resource' => 1, // Permission "delete_document" is required to delete or unpublish a static resource. 67 | 'delete_symlink' => 1, // Permission "delete_document" is required to delete or unpublish a symbolic link. 68 | 'delete_user' => 1, // Disable and delete users. 69 | 'delete_weblink' => 1, // Permission "delete_document" is required to delete or unpublish a web link. 70 | 'directory_create' => 1, // Create directories in the file system. 71 | 'directory_list' => 1, // View the list of subdirectories in a file system directory. 72 | 'directory_remove' => 1, // Delete directories in the file system. 73 | 'directory_update' => 1, // Rename directories in the file system. 74 | 'edit_document' => 1, // Edit resources. 75 | 'edit_locked' => 1, // Allows the user to edit locked resources. 76 | 'edit_static_resource' => 1, // Permission "edit_document" is required to edit static resources. 77 | 'edit_symlink' => 1, // Permission "edit_document" is required to edit symbolic links. 78 | 'edit_user' => 1, // Edit users. 79 | 'edit_weblink' => 1, // Permission "edit_document" is required to edit web links. 80 | 'empty_cache' => 1, // Clear the site cache. 81 | 'file_create' => 1, // Create files. 82 | 'file_list' => 1, // View the list of files in folders. 83 | 'file_remove' => 1, // Delete files. 84 | 'file_tree' => 1, // View the file tree in the left navigation panel. 85 | 'file_update' => 1, // Modify files. 86 | 'file_upload' => 1, // Upload files to a folder. 87 | 'file_view' => 1, // View file content. 88 | 'list' => 1, // Ability to "list" any object. "List" means retrieving a collection of objects. 89 | 'load' => 1, // Ability to load objects or return them as object instances in general. 90 | 'logout' => 1, // Ability to log out as a user. 91 | 'menu_trash' => 1, // Display the "Deleted Resources Management" item in the top menu. 92 | 'new_document' => 1, // Create resources. 93 | 'new_document_in_root' => 1, // Create resources at the root level. 94 | 'new_static_resource' => 1, // Create static resources. 95 | 'new_symlink' => 1, // Create symbolic links. 96 | 'new_weblink' => 1, // Create new web links. 97 | 'publish_document' => 1, // Publish and unpublish resources. 98 | 'purge_deleted' => 1, // Empty the trash bin. 99 | 'remove' => 1, // Ability to delete objects. 100 | 'remove_locks' => 1, // Remove all locks on the site. 101 | 'resource_quick_create' => 1, // Use "Quick Create Resource" in the resource tree on the left. 102 | 'resource_quick_update' => 1, // Use "Quick Update Resource" in the resource tree on the left. 103 | 'resource_tree' => 1, // View the Resource Tree in the left navigation panel. 104 | 'save' => 1, // Ability to save objects. 105 | 'save_document' => 1, // Save resources. 106 | 'search' => 1, // Use the "Search" page. 107 | 'settings' => 0, // View and edit System Settings. 108 | 'source_view' => 1, // View file sources. 109 | 'tree_show_resource_ids' => 1, // Show IDs in the Resource Tree. 110 | 'undelete_document' => 1, // Ability to undelete resources. 111 | 'unpublish_document' => 1, // Unpublish resources. 112 | 'view' => 1, // Ability to "view" objects. 113 | 'view_document' => 1, // View resources. 114 | 'view_tv' => 1, // View TVs (Template Variables). 115 | 'view_unpublished' => 1, // View unpublished resources. 116 | 'view_user' => 1, // View users. 117 | ], 118 | 'media_source' => [ 119 | 'name' => 'Manager', 120 | 'bind_tvs' => true, 121 | 'source_path' => 'assets/uploads/', 122 | 'access' => [ 123 | 'authority' => 9, 124 | 'policy' => 8, 125 | 'context_key' => '', 126 | ], 127 | ] 128 | ], 129 | 'seo' => [ 130 | 'users' => [[ 131 | 'username' => 'seo', 132 | 'password' => '', 133 | 'email' => '', 134 | ]], 135 | 'context_key' => 'mgr', 136 | 'group_name' => 'Seo', 137 | 'access_role_authority' => 9, 138 | 'access_policy_template_inherit' => 'Manager', 139 | 'permissions' => [ 140 | 'components' => 1, // View the "Packages" menu. 141 | 'delete_document' => 1, // Delete and move resources. 142 | 'view_snippet' => 1, // View snippets. 143 | 'new_snippet' => 0, // To create a new Snippet. 144 | 'edit_snippet' => 0, // To edit any Snippets. 145 | 'save_snippet' => 0, // To save any Snippets. 146 | 'view_chunk' => 1, // View chunks. 147 | 'edit_chunk' => 1, // Edit chunks. 148 | 'save_chunk' => 1, // To save any Chunks. 149 | 'edit_locked' => 1, // Allows the user to edit locked resources. 150 | 'edit_template' => 1, // Edit templates. 151 | 'save_template' => 1, // To save any Templates. 152 | 'edit_tv' => 1, // Edit TVs (Template Variables). 153 | 'element_tree' => 1, // View the element tree in the left navigation panel. 154 | 'error_log_view' => 1, // View the error log. 155 | 'events' => 1, // View system events. 156 | 'flush_sessions' => 1, // Reset all site sessions. 157 | 'lexicons' => 1, // View or edit "Lexicon Management." 158 | 'logs' => 1, // View the "System Management Log." 159 | 'menu_tools' => 1, // Display the "Tools" menu item in the top menu. 160 | 'menu_trash' => 1, // Display the "Deleted Resources Management" menu item in the top menu. 161 | 'remove_locks' => 1, // Remove all locks on the site. 162 | 'resourcegroup_resource_list' => 1, // View resources in a resource group. 163 | 'resourcegroup_view' => 1, // View resource groups. 164 | 'settings' => 0, // View and edit system settings. 165 | 'steal_locks' => 1, // "Steal" resource locks to gain control. 166 | 'tree_show_element_ids' => 1, // Show IDs in the element tree. 167 | 'usergroup_user_list' => 1, // View the list of users in a user group. 168 | 'usergroup_view' => 1, // View user groups. 169 | 'view_category' => 1, // View categories. 170 | 'view_context' => 1, // View contexts. 171 | 'view_eventlog' => 1, // View the event log. 172 | 'view_offline' => 1, // View the site when it is in offline mode. 173 | 'view_plugin' => 1, // View plugins. 174 | 'view_propertyset' => 1, // View property sets. 175 | 'view_tv' => 1, // View TVs (Template Variables). 176 | ], 177 | 'media_source' => [ 178 | 'join' => 'Manager' 179 | ] 180 | ] 181 | 182 | ]; 183 | 184 | $config['ms2'] = [ 185 | 'demo' => [ 186 | 'reset' => false, 187 | 'enable' => false, 188 | 'vendors' => true, 189 | 'products' => true, 190 | 'categories' => true, 191 | ], 192 | 'templates' => [ 193 | 'cart', 194 | 'product', 195 | 'category', 196 | ], 197 | 'pages' => [ 198 | 'category' => [ 199 | 'pagetitle' => 'Category', 200 | 'template' => 'category', 201 | 'class_key' => 'msCategory', 202 | 'content' => '', 203 | 'hidemenu' => 0, 204 | 'publishedon' => 1, 205 | ], 206 | 'cart' => [ 207 | 'pagetitle' => 'Cart', 208 | 'template' => 'cart', 209 | 'hidemenu' => 0, 210 | 'publishedon' => 1, 211 | ], 212 | ], 213 | ]; 214 | 215 | return $config; -------------------------------------------------------------------------------- /docker/modx/tools/configurator/example.config.inc.php: -------------------------------------------------------------------------------- 1 | [ 14 | 'name' => 'modstore.pro', 15 | 'service_url' => 'https://modstore.pro/extras/', 16 | 'username' => '', 17 | 'api_key' => '', 18 | ], 19 | 'modxkit.com' => [ 20 | 'name' => 'modxkit.com', 21 | 'service_url' => 'https://modxkit.com/extras/', 22 | 'username' => '', 23 | 'api_key' => '', 24 | ], 25 | ]; 26 | $config['install_packages'] = [ 27 | 'ace' => [ 28 | 'name' => 'Ace', 29 | 'version' => '', 30 | 'provider' => 'modx.com', 31 | ], 32 | 'controlerrorlog' => [ 33 | 'name' => 'controlErrorLog', 34 | ], 35 | 'console' => [ 36 | 'name' => 'Console', 37 | ], 38 | 'VersionX' => [ 39 | 'name' => 'versionx', 40 | ], 41 | 'clientconfig' => [ 42 | 'name' => 'ClientConfig', 43 | ], 44 | 'debugparser' => [ 45 | 'name' => 'debugParser', 46 | ], 47 | 'tagelementplugin' => [ 48 | 'name' => 'tagElementPlugin', 49 | ], 50 | 'upgrademodx' => [ 51 | 'name' => 'UpgradeMODX', 52 | ], 53 | 'translit' => [ 54 | 'name' => 'translit', 55 | ], 56 | 'collections' => [ 57 | 'name' => 'Collections', 58 | ], 59 | 'tinymcerte' => [ 60 | 'name' => 'TinyMCE Rich Text Editor', 61 | ], 62 | 'admintools' => [ 63 | 'name' => 'AdminTools', 64 | 'provider' => 'modstore.pro', 65 | ], 66 | 'moddevtools' => [ 67 | 'name' => 'modDevTools', 68 | 'provider' => 'modstore.pro', 69 | ], 70 | 'frontendmanager' => [ 71 | 'name' => 'frontendManager', 72 | 'provider' => 'modstore.pro', 73 | ], 74 | 'staticelementslive' => [ 75 | 'name' => 'StaticElementsLive', 76 | 'provider' => 'modstore.pro', 77 | ], 78 | 'theme.bootstrap' => [ 79 | 'name' => 'Theme.Bootstrap', 80 | 'provider' => 'modstore.pro', 81 | ], 82 | 'minishop2' => [ 83 | 'name' => 'miniShop2', 84 | 'provider' => 'modstore.pro', 85 | ], 86 | ]; 87 | 88 | $config['set_options'] = [ 89 | 'friendly_urls' => 1, 90 | 'use_alias_path' => 1, 91 | 'friendly_alias_translit' => 'russian', 92 | ]; 93 | 94 | $config['grant_access_user'] = [ 95 | 'manager' => [ 96 | 'users' => [[ 97 | 'username' => 'manager', 98 | 'password' => '', 99 | 'email' => '', 100 | ]], 101 | 'context_key' => 'mgr', 102 | 'group_name' => 'Manager', 103 | 'access_role_name' => '', 104 | 'access_role_authority' => 9, 105 | 'access_policy_name' => '', 106 | 'access_policy_template_name' => '', 107 | 'access_policy_template_override' => 'AdministratorTemplate', 108 | // see more (en) https://gist.github.com/Prihod/09ff88088c67dff4a48290344a46ec9b 109 | // see more (ru) https://gist.github.com/Prihod/ce52b1cb0e12ac9b5f14bc94dfcd03a5 110 | 'permissions' => [ 111 | 'frames' => 1, // To use the MODX Manager UI at all. 112 | 'home' => 1, // To view the Welcome page. 113 | 'menu_user' => 1, // Show the top menu item "User". 114 | 'view_template' => 1, // To view any Templates. 115 | 'class_map' => 1, // To view a list of classes in the Class Map. 116 | 'change_password' => 1, // The user can change their password. 117 | 'change_profile' => 1, // The user can edit their profile. 118 | 'countries' => 1, // View the list of countries. 119 | 'create' => 1, // Ability to "create" new objects. 120 | 'delete_document' => 1, // Delete and move resources. 121 | 'delete_static_resource' => 1, // Permission "delete_document" is required to delete or unpublish a static resource. 122 | 'delete_symlink' => 1, // Permission "delete_document" is required to delete or unpublish a symbolic link. 123 | 'delete_user' => 1, // Disable and delete users. 124 | 'delete_weblink' => 1, // Permission "delete_document" is required to delete or unpublish a web link. 125 | 'directory_create' => 1, // Create directories in the file system. 126 | 'directory_list' => 1, // View the list of subdirectories in a file system directory. 127 | 'directory_remove' => 1, // Delete directories in the file system. 128 | 'directory_update' => 1, // Rename directories in the file system. 129 | 'edit_document' => 1, // Edit resources. 130 | 'edit_locked' => 1, // Allows the user to edit locked resources. 131 | 'edit_static_resource' => 1, // Permission "edit_document" is required to edit static resources. 132 | 'edit_symlink' => 1, // Permission "edit_document" is required to edit symbolic links. 133 | 'edit_user' => 1, // Edit users. 134 | 'edit_weblink' => 1, // Permission "edit_document" is required to edit web links. 135 | 'empty_cache' => 1, // Clear the site cache. 136 | 'file_create' => 1, // Create files. 137 | 'file_list' => 1, // View the list of files in folders. 138 | 'file_remove' => 1, // Delete files. 139 | 'file_tree' => 1, // View the file tree in the left navigation panel. 140 | 'file_update' => 1, // Modify files. 141 | 'file_upload' => 1, // Upload files to a folder. 142 | 'file_view' => 1, // View file content. 143 | 'list' => 1, // Ability to "list" any object. "List" means retrieving a collection of objects. 144 | 'load' => 1, // Ability to load objects or return them as object instances in general. 145 | 'logout' => 1, // Ability to log out as a user. 146 | 'menu_trash' => 1, // Display the "Deleted Resources Management" item in the top menu. 147 | 'new_document' => 1, // Create resources. 148 | 'new_document_in_root' => 1, // Create resources at the root level. 149 | 'new_static_resource' => 1, // Create static resources. 150 | 'new_symlink' => 1, // Create symbolic links. 151 | 'new_weblink' => 1, // Create new web links. 152 | 'publish_document' => 1, // Publish and unpublish resources. 153 | 'purge_deleted' => 1, // Empty the trash bin. 154 | 'remove' => 1, // Ability to delete objects. 155 | 'remove_locks' => 1, // Remove all locks on the site. 156 | 'resource_quick_create' => 1, // Use "Quick Create Resource" in the resource tree on the left. 157 | 'resource_quick_update' => 1, // Use "Quick Update Resource" in the resource tree on the left. 158 | 'resource_tree' => 1, // View the Resource Tree in the left navigation panel. 159 | 'save' => 1, // Ability to save objects. 160 | 'save_document' => 1, // Save resources. 161 | 'search' => 1, // Use the "Search" page. 162 | 'settings' => 0, // View and edit System Settings. 163 | 'source_view' => 1, // View file sources. 164 | 'tree_show_resource_ids' => 1, // Show IDs in the Resource Tree. 165 | 'undelete_document' => 1, // Ability to undelete resources. 166 | 'unpublish_document' => 1, // Unpublish resources. 167 | 'view' => 1, // Ability to "view" objects. 168 | 'view_document' => 1, // View resources. 169 | 'view_tv' => 1, // View TVs (Template Variables). 170 | 'view_unpublished' => 1, // View unpublished resources. 171 | 'view_user' => 1, // View users. 172 | ], 173 | 'media_source' => [ 174 | 'name' => 'Manager', 175 | 'bind_tvs' => true, 176 | 'source_path' => 'assets/uploads/', 177 | 'access' => [ 178 | 'authority' => 9, 179 | 'policy' => 8, 180 | 'context_key' => '', 181 | ], 182 | ] 183 | ], 184 | 'seo' => [ 185 | 'users' => [[ 186 | 'username' => 'seo', 187 | 'password' => '', 188 | 'email' => '', 189 | ]], 190 | 'context_key' => 'mgr', 191 | 'group_name' => 'Seo', 192 | 'access_role_authority' => 9, 193 | 'access_policy_template_inherit' => 'Manager', 194 | 'permissions' => [ 195 | 'components' => 1, // View the "Packages" menu. 196 | 'delete_document' => 1, // Delete and move resources. 197 | 'view_snippet' => 1, // View snippets. 198 | 'new_snippet' => 0, // To create a new Snippet. 199 | 'edit_snippet' => 0, // To edit any Snippets. 200 | 'save_snippet' => 0, // To save any Snippets. 201 | 'view_chunk' => 1, // View chunks. 202 | 'edit_chunk' => 1, // Edit chunks. 203 | 'save_chunk' => 1, // To save any Chunks. 204 | 'edit_locked' => 1, // Allows the user to edit locked resources. 205 | 'edit_template' => 1, // Edit templates. 206 | 'save_template' => 1, // To save any Templates. 207 | 'edit_tv' => 1, // Edit TVs (Template Variables). 208 | 'element_tree' => 1, // View the element tree in the left navigation panel. 209 | 'error_log_view' => 1, // View the error log. 210 | 'events' => 1, // View system events. 211 | 'flush_sessions' => 1, // Reset all site sessions. 212 | 'lexicons' => 1, // View or edit "Lexicon Management." 213 | 'logs' => 1, // View the "System Management Log." 214 | 'menu_tools' => 1, // Display the "Tools" menu item in the top menu. 215 | 'menu_trash' => 1, // Display the "Deleted Resources Management" menu item in the top menu. 216 | 'remove_locks' => 1, // Remove all locks on the site. 217 | 'resourcegroup_resource_list' => 1, // View resources in a resource group. 218 | 'resourcegroup_view' => 1, // View resource groups. 219 | 'settings' => 0, // View and edit system settings. 220 | 'steal_locks' => 1, // "Steal" resource locks to gain control. 221 | 'tree_show_element_ids' => 1, // Show IDs in the element tree. 222 | 'usergroup_user_list' => 1, // View the list of users in a user group. 223 | 'usergroup_view' => 1, // View user groups. 224 | 'view_category' => 1, // View categories. 225 | 'view_context' => 1, // View contexts. 226 | 'view_eventlog' => 1, // View the event log. 227 | 'view_offline' => 1, // View the site when it is in offline mode. 228 | 'view_plugin' => 1, // View plugins. 229 | 'view_propertyset' => 1, // View property sets. 230 | 'view_tv' => 1, // View TVs (Template Variables). 231 | ], 232 | 'media_source' => [ 233 | 'join' => 'Manager' 234 | ] 235 | ] 236 | 237 | ]; 238 | 239 | $config['ms2'] = [ 240 | 'demo' => [ 241 | 'reset' => false, 242 | 'enable' => true, 243 | 'vendors' => true, 244 | 'products' => true, 245 | 'categories' => true, 246 | ], 247 | 'templates' => [ 248 | 'cart', 249 | 'product', 250 | 'category', 251 | ], 252 | 'pages' => [ 253 | 'category' => [ 254 | 'pagetitle' => 'Category', 255 | 'template' => 'category', 256 | 'class_key' => 'msCategory', 257 | 'content' => '', 258 | 'hidemenu' => 0, 259 | 'publishedon' => 1, 260 | ], 261 | 'cart' => [ 262 | 'pagetitle' => 'Cart', 263 | 'template' => 'cart', 264 | 'hidemenu' => 0, 265 | 'publishedon' => 1, 266 | ], 267 | ], 268 | ]; 269 | 270 | return $config; -------------------------------------------------------------------------------- /docker/modx/tools/configurator/run.php: -------------------------------------------------------------------------------- 1 | getService('error', 'error.modError'); 16 | $modx->setLogLevel(\modX::LOG_LEVEL_ERROR); 17 | $modx->setLogTarget('FILE'); 18 | 19 | $config = require(__DIR__ . '/config.inc.php'); 20 | $runner = new Runner($modx, $config); 21 | $runner->run(); -------------------------------------------------------------------------------- /docker/modx/tools/configurator/src/Runner/Runner.php: -------------------------------------------------------------------------------- 1 | initialize($modx, $properties); 20 | } 21 | 22 | public function run(): void 23 | { 24 | $this->loadTasks(); 25 | if (empty($this->tasks)) { 26 | return; 27 | } 28 | Logger::info("Start configurator Modx"); 29 | foreach ($this->tasks as $task) { 30 | $this->runTask($task); 31 | $this->modx->reloadConfig(); 32 | } 33 | Logger::info("Finish configurator Modx"); 34 | } 35 | 36 | protected function loadTasks(): array 37 | { 38 | $handlers = $this->getProperty('tasks', []); 39 | 40 | foreach ($handlers as $handler) { 41 | $this->addTask($handler); 42 | } 43 | 44 | return $this->tasks; 45 | } 46 | 47 | protected function addTask(string $handler): void 48 | { 49 | try { 50 | $fullClass = 'App\\Tasks\\' . $handler; 51 | if (!class_exists($fullClass)) { 52 | Logger::error("Class '{$fullClass}' not found."); 53 | return; 54 | } 55 | $task = new $fullClass($this->modx, $this->getProperties()); 56 | if (!$task instanceof Task) { 57 | Logger::error("Task handler error: The handler '{$handler}' must be an instance of Task."); 58 | return; 59 | } 60 | 61 | $this->tasks[] = $task; 62 | } catch (\Exception $e) { 63 | Logger::error($e->getMessage()); 64 | } 65 | } 66 | 67 | protected function runTask(Task $task): void 68 | { 69 | try { 70 | Logger::info("Start execute task: '{$task->getName()}'"); 71 | $task->execute(); 72 | Logger::info("Finish execute task: '{$task->getName()}'"); 73 | } catch (\Exception $e) { 74 | Logger::error("Error in task '{$task->getName()}': " . $e->getMessage()); 75 | } 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /docker/modx/tools/configurator/src/Runner/RunnerInterface.php: -------------------------------------------------------------------------------- 1 | modx->lexicon->load('policy'); 20 | $config = $this->getProperty('grant_access_user'); 21 | if (empty($config)) { 22 | return; 23 | } 24 | 25 | foreach ($config as $key => $data) { 26 | Logger::info("Grant access for: {$key}"); 27 | $users = $data['users'] ?? []; 28 | $groupName = $data['group_name'] ?? ''; 29 | $permissions = $data['permissions'] ?? []; 30 | $contextKey = $data['context_key'] ?? null; 31 | $roleAuthority = $data['access_role_authority'] ?? 9; 32 | $roleName = empty($data['access_role_name']) ? $groupName : $data['access_role_name']; 33 | $policyName = empty($data['access_policy_name']) ? $groupName : $data['access_policy_name']; 34 | $templateName = empty($data['access_policy_template_name']) ? $groupName : $data['access_policy_template_name']; 35 | $mediaSourceOptions = $data['media_source'] ?? null; 36 | $sourceTemplate = $this->detectSourceAccessPolicyTemplate($data); 37 | 38 | if (!$contextKey || !$groupName || !$sourceTemplate) { 39 | continue; 40 | } 41 | 42 | 43 | $template = $this->getOrDuplicateAccessPolicyTemplate($templateName, $sourceTemplate->get('id')); 44 | if (!$template) { 45 | continue; 46 | } 47 | 48 | 49 | $permissions = $this->prepareTemplatePermissions($permissions, $sourceTemplate->get('id'), $data); 50 | $accessPolicy = $this->getOrCreateAccessPolicy($policyName, $template->get('id'), $permissions); 51 | if (!$accessPolicy) { 52 | continue; 53 | } 54 | 55 | $role = $this->getOrCreateUserGroupRole($roleName, $roleAuthority); 56 | if (!$role) { 57 | continue; 58 | } 59 | 60 | $userGroup = $this->getOrCreateUserGroup($groupName); 61 | if (!$userGroup) { 62 | continue; 63 | } 64 | 65 | $contextData = [ 66 | 'target' => $contextKey, 67 | 'authority' => $roleAuthority, 68 | 'policy' => $accessPolicy->get('id'), 69 | 'principal' => $userGroup->get('id'), 70 | ]; 71 | 72 | $context = $this->getOrCreateUserGroupAccessContext($contextData); 73 | if (!$context) { 74 | continue; 75 | } 76 | 77 | if ($contextKey === 'mgr') { 78 | $this->updateUserGroupAccessContext($userGroup->get('id'), 'web', [ 79 | 'authority' => $roleAuthority, 80 | 'policy' => 2, // access policy - Administrator. 81 | ]); 82 | } 83 | 84 | $groupMember = array( 85 | 'user_group' => $userGroup->get('id'), 86 | 'role' => $role->get('id'), 87 | ); 88 | 89 | foreach ($users as $user) { 90 | $this->getOrCreateUser($user, $groupMember); 91 | } 92 | 93 | if (!empty($mediaSourceOptions)) { 94 | if (empty($mediaSourceOptions['join'])) { 95 | $this->createMediaSource($mediaSourceOptions, $userGroup->get('id')); 96 | } else { 97 | $this->joinMediaSource($mediaSourceOptions['join'], $userGroup->get('id'), $mediaSourceOptions); 98 | } 99 | } 100 | } 101 | 102 | } 103 | 104 | protected function prepareTemplatePermissions(array $permissions, int $templateId = 0, array $options = []): ?array 105 | { 106 | if (!empty($options['access_policy_template_override'])) { 107 | $defaultPermissions = $this->getAccessPolicyTemplatePermissions($templateId, 0); 108 | } else { 109 | $defaultPermissions = $this->getAccessPolicyPermissions($templateId); 110 | } 111 | return array_merge($defaultPermissions, $permissions); 112 | } 113 | 114 | protected function detectSourceAccessPolicyTemplate(array $options = []): ?object 115 | { 116 | if (!empty($options['access_policy_template_override'])) { 117 | return $this->findAccessPolicyTemplate($options['access_policy_template_override']); 118 | 119 | } else if (!empty($options['access_policy_template_inherit'])) { 120 | return $this->findAccessPolicyTemplate($options['access_policy_template_inherit']); 121 | } 122 | return null; 123 | } 124 | 125 | protected function joinMediaSource(string $mediaSourceName, int $userGroupId, array $options = []): ?object 126 | { 127 | $mediaSource = $this->findMediaSource($mediaSourceName); 128 | if (!$mediaSource) { 129 | return null; 130 | } 131 | 132 | $access = $options['access'] ?? []; 133 | $access = array_merge([ 134 | 'target' => $mediaSource->get('id'), 135 | 'authority' => 9, 136 | 'policy' => 8, 137 | 'principal_class' => 'modUserGroup', 138 | 'principal' => $userGroupId, 139 | ], $access); 140 | 141 | if (!$this->getOrCreateAccessMediaSource($access)) { 142 | return null; 143 | } 144 | 145 | return $mediaSource; 146 | } 147 | 148 | protected function createMediaSource(array $options, int $userGroupId): void 149 | { 150 | 151 | $name = $options['name'] ?? ''; 152 | $access = $options['access'] ?? []; 153 | $isBindTvs = $options['bind_tvs'] ?? false; 154 | $sourcePath = $options['source_path'] ?? ''; 155 | 156 | if ($sourcePath) { 157 | $mediaSource = $this->getOrCreateFileMediaSource($name, $sourcePath); 158 | if (!$mediaSource) { 159 | return; 160 | } 161 | 162 | $access = array_merge([ 163 | 'target' => $mediaSource->get('id'), 164 | 'principal_class' => 'modUserGroup', 165 | 'principal' => $userGroupId, 166 | ], $access); 167 | 168 | $this->getOrCreateAccessMediaSource($access); 169 | 170 | $adminAccess = array( 171 | 'target' => 1, 172 | 'principal_class' => 'modUserGroup', 173 | 'principal' => 1, 174 | 'authority' => 0, 175 | 'policy' => 8, 176 | 'context_key' => '', 177 | ); 178 | $this->getOrCreateAccessMediaSource($adminAccess); 179 | 180 | if ($isBindTvs) { 181 | $this->bindMediaSourceTvs($mediaSource->get('id')); 182 | } 183 | } 184 | } 185 | } -------------------------------------------------------------------------------- /docker/modx/tools/configurator/src/Tasks/InstallPackagesTask.php: -------------------------------------------------------------------------------- 1 | getProperty('install_packages', []); 26 | if (empty($packages)) { 27 | return; 28 | } 29 | 30 | foreach ($packages as $data) { 31 | $name = $data['name'] ?? ''; 32 | $version = $data['version'] ?? ''; 33 | /** @var \modTransportPackage $package */ 34 | $package = $this->getPackage($name); 35 | if ($package && $package->compareVersion($version, '<=')) { 36 | continue; 37 | } 38 | 39 | if ($this->installPackage($name, $data)) { 40 | Logger::info("Installed package '{$name}'"); 41 | } 42 | } 43 | } 44 | } -------------------------------------------------------------------------------- /docker/modx/tools/configurator/src/Tasks/SetOptionsTask.php: -------------------------------------------------------------------------------- 1 | getProperty('set_options', []); 20 | if (empty($options)) { 21 | return; 22 | } 23 | foreach ($options as $key => $val) { 24 | if ($this->setOption($key, $val)) { 25 | Logger::info("Set option: '{$key}' value: '{$val}'"); 26 | } else { 27 | Logger::error("Error set option: '{$key}' value: '{$val}'"); 28 | } 29 | } 30 | } 31 | } -------------------------------------------------------------------------------- /docker/modx/tools/configurator/src/Tasks/Task.php: -------------------------------------------------------------------------------- 1 | initialize($modx, $properties); 18 | } 19 | 20 | protected function getStoragePath(): string 21 | { 22 | return dirname(__FILE__, 3) . '/storage/'; 23 | } 24 | 25 | protected function getImagePath(): string 26 | { 27 | $path = MODX_ASSETS_PATH . 'images/'; 28 | if (!file_exists($path)) { 29 | $this->modx->cacheManager->writeTree($path); 30 | } 31 | return $path; 32 | } 33 | 34 | protected function readCsv(string $file, callable $callback, string $delimiter = ";"): void 35 | { 36 | if (!file_exists($file) || !is_readable($file)) { 37 | throw new \Exception("File not found or not readable: $file"); 38 | } 39 | 40 | if (($handle = fopen($file, 'r')) !== false) { 41 | while (($row = fgetcsv($handle, 0, ";", '"')) !== false) { 42 | $callback($row); 43 | } 44 | fclose($handle); 45 | } else { 46 | throw new \Exception("Failed to open file: $file"); 47 | } 48 | } 49 | 50 | protected function saveArrayToCsv(string $file, array $data): void 51 | { 52 | if (($handle = fopen($file, 'w')) !== false) { 53 | foreach ($data as $row) { 54 | fputcsv($handle, $row, ";"); 55 | } 56 | fclose($handle); 57 | return; 58 | } 59 | throw new \Exception("Failed to open file: $file"); 60 | } 61 | 62 | } -------------------------------------------------------------------------------- /docker/modx/tools/configurator/src/Tasks/TaskInterface.php: -------------------------------------------------------------------------------- 1 | getProperty('transport_providers', []); 21 | foreach ($providers as $provider) { 22 | if ( 23 | empty($provider['username']) || 24 | empty($provider['api_key']) || 25 | $this->hasProvider($provider['name']) 26 | ) { 27 | continue; 28 | } 29 | if ($this->addProvider($provider)) { 30 | Logger::info("Added provider: '{$provider['name']}'"); 31 | } 32 | } 33 | } 34 | 35 | } -------------------------------------------------------------------------------- /docker/modx/tools/configurator/src/Traits/DocumentTrait.php: -------------------------------------------------------------------------------- 1 | findModDocument($name)) { 19 | $this->modx->error->reset(); 20 | /** @var \modProcessorResponse $response */ 21 | $response = $this->modx->runProcessor('resource/create', $params); 22 | if ($response->isError()) { 23 | Logger::error($response->getResponse()); 24 | return null; 25 | } 26 | $obj = $response->getObject(); 27 | $doc = $this->getModDocument($obj['id']); 28 | } 29 | return $doc; 30 | } 31 | 32 | protected function findModDocument(string $name): ?object 33 | { 34 | return $this->modx->getObject('modResource', ['pagetitle' => $name]); 35 | } 36 | 37 | protected function getModDocument(int $id): ?object 38 | { 39 | return $this->modx->getObject('modResource', $id); 40 | } 41 | } -------------------------------------------------------------------------------- /docker/modx/tools/configurator/src/Traits/ElementsTrait.php: -------------------------------------------------------------------------------- 1 | findModTemplate($name)) { 13 | $template = $this->modx->newObject('modTemplate'); 14 | $template->fromArray($params); 15 | $template->set('templatename', $name); 16 | $template->set('content', $content); 17 | $template->set('category', $categoryId); 18 | return $template->save() ? $template : null; 19 | } 20 | return $template; 21 | } 22 | 23 | protected function findModTemplate(string $name): ?object 24 | { 25 | return $this->modx->getObject('modTemplate', ['templatename' => $name]); 26 | } 27 | 28 | protected function getModTemplate(int $id): ?object 29 | { 30 | return $this->modx->getObject('modTemplate', $id); 31 | } 32 | 33 | protected function createModChunk(string $name, string $content = '', int $categoryId = 0, array $params = []):? object 34 | { 35 | if (!$chunk = $this->findModChunk($name)) { 36 | $chunk = $this->modx->newObject('modChunk'); 37 | $chunk->fromArray($params); 38 | $chunk->set('name', $name); 39 | $chunk->set('snippet', $content); 40 | $chunk->set('category', $categoryId); 41 | return $chunk->save() ? $chunk : null; 42 | } 43 | return $chunk; 44 | } 45 | 46 | protected function findModChunk(string $name): ?object 47 | { 48 | return $this->modx->getObject('modChunk', ['name' => $name]); 49 | } 50 | 51 | protected function createModCategory(string $name, int $parent = 0, array $params = []):? object 52 | { 53 | if (!$category = $this->findModCategory($name)) { 54 | $category = $this->modx->newObject('modCategory'); 55 | $category->fromArray($params); 56 | $category->set('name', $name); 57 | $category->set('parent', $parent); 58 | return $category->save() ? $category : null; 59 | } 60 | return $category; 61 | } 62 | 63 | protected function findModCategory(string $name): ?object 64 | { 65 | return $this->modx->getObject('modCategory', ['name' => $name]); 66 | } 67 | 68 | } -------------------------------------------------------------------------------- /docker/modx/tools/configurator/src/Traits/InitializeTrait.php: -------------------------------------------------------------------------------- 1 | modx = $modx; 12 | if ($this->hasTrait('App\Traits\PropertiesTrait')) { 13 | $this->setProperties($properties); 14 | } 15 | } 16 | 17 | protected function hasTrait(string $trait): bool 18 | { 19 | $class = static::class; 20 | do { 21 | if (in_array($trait, class_uses($class))) { 22 | return true; 23 | } 24 | } while ($class = get_parent_class($class)); 25 | 26 | return false; 27 | } 28 | 29 | } -------------------------------------------------------------------------------- /docker/modx/tools/configurator/src/Traits/OptionTrait.php: -------------------------------------------------------------------------------- 1 | modx->getObject('modSystemSetting', ['key' => $key])) { 12 | return false; 13 | } 14 | 15 | $setting->set('value', $value); 16 | $saved = $setting->save(); 17 | if ($saved) { 18 | $this->modx->setOption($key, $value); 19 | if ($clearCache) { 20 | $this->modx->cacheManager->refresh(array('system_settings' => array())); 21 | } 22 | } 23 | 24 | return $saved; 25 | } 26 | 27 | protected function getOption(string $key, $default = null) 28 | { 29 | if (!$setting = $this->modx->getObject('modSystemSetting', ['key' => $key])) { 30 | return $default; 31 | } 32 | $value = $setting->get('value'); 33 | if (is_string($value)) { 34 | $value = $this->normalizePath($value); 35 | $value = $this->normalizeUrl($value); 36 | } 37 | return $value; 38 | } 39 | 40 | private function normalizePath(string $path): string 41 | { 42 | return str_replace(array( 43 | '{base_path}', 44 | '{core_path}', 45 | '{assets_path}', 46 | ), array( 47 | MODX_BASE_PATH, 48 | MODX_CORE_PATH, 49 | MODX_ASSETS_PATH, 50 | ), $path); 51 | } 52 | 53 | private function normalizeUrl(string $url): string 54 | { 55 | return str_replace(array( 56 | '{base_url}', 57 | '{core_url}', 58 | '{assets_url}', 59 | ), array( 60 | MODX_BASE_PATH, 61 | MODX_CORE_PATH, 62 | MODX_ASSETS_PATH, 63 | ), $url); 64 | } 65 | 66 | } -------------------------------------------------------------------------------- /docker/modx/tools/configurator/src/Traits/PropertiesTrait.php: -------------------------------------------------------------------------------- 1 | properties[$key] = $value; 12 | } 13 | 14 | public function getProperty(string $key, $default = null, bool $skipEmpty = false) 15 | { 16 | $value = $this->properties[$key] ?? $default; 17 | return ($skipEmpty && empty($value)) ? $default : $value; 18 | } 19 | 20 | public function hasProperty(string $key): bool 21 | { 22 | return isset($this->properties[$key]); 23 | } 24 | 25 | public function unsetProperty(string $key): void 26 | { 27 | unset($this->properties[$key]); 28 | } 29 | 30 | public function getProperties(): array 31 | { 32 | return $this->properties; 33 | } 34 | 35 | public function setProperties(array $properties = []): void 36 | { 37 | $this->properties = array_merge($this->properties, $properties); 38 | } 39 | 40 | public function setDefaultProperties(array $properties = []): array 41 | { 42 | $this->properties = array_merge($properties, $this->properties); 43 | return $this->properties; 44 | } 45 | } -------------------------------------------------------------------------------- /docker/modx/tools/configurator/src/Traits/TransportProviderTrait.php: -------------------------------------------------------------------------------- 1 | modx->newQuery('transport.modTransportPackage'); 16 | $q->where([ 17 | 'package_name:=' => $name, 18 | 'OR:package_name:=' => strtolower($name) 19 | ]); 20 | return $this->modx->getObject('transport.modTransportPackage', $q); 21 | } 22 | 23 | protected function installPackage(string $name, array $options = []): bool 24 | { 25 | $providerName = $options['provider'] ?? 'modx.com'; 26 | $providers = $this->getTransportProviders(); 27 | $provider = $providers[$providerName] ?? null; 28 | if (!$provider) { 29 | Logger::error("Provider '{$providerName}' for package '{$name}' not found!"); 30 | return false; 31 | } 32 | 33 | $this->modx->getVersionData(); 34 | $productVersion = $this->modx->version['code_name'] . '-' . $this->modx->version['full_version']; 35 | $response = $provider->request('package', 'GET', [ 36 | 'supports' => $productVersion, 37 | 'query' => $name, 38 | ]); 39 | 40 | if (!empty($response)) { 41 | $foundPackages = simplexml_load_string($response->response); 42 | foreach ($foundPackages as $foundPackage) { 43 | /** @var \modTransportPackage $foundPackage */ 44 | /** @noinspection PhpUndefinedFieldInspection */ 45 | if (preg_match('#^' . $name . '\b#i', $foundPackage->name)) { 46 | $sig = explode('-', $foundPackage->signature); 47 | $versionSignature = explode('.', $sig[1]); 48 | /** @noinspection PhpUndefinedFieldInspection */ 49 | $url = $foundPackage->location; 50 | $dst = $this->modx->getOption('core_path') . 'packages/' . $foundPackage->signature . '.transport.zip'; 51 | if (!$this->downloadPackage($url, $dst)) { 52 | Logger::error("Could not download package '{$name}'!"); 53 | return false; 54 | } 55 | 56 | $package = $this->modx->newObject('transport.modTransportPackage'); 57 | $package->set('signature', $foundPackage->signature); 58 | /** @noinspection PhpUndefinedFieldInspection */ 59 | $package->fromArray([ 60 | 'created' => date('Y-m-d h:i:s'), 61 | 'updated' => null, 62 | 'state' => 1, 63 | 'workspace' => 1, 64 | 'provider' => $provider->get('id'), 65 | 'source' => $foundPackage->signature . '.transport.zip', 66 | 'package_name' => $name, 67 | 'version_major' => $versionSignature[0], 68 | 'version_minor' => !empty($versionSignature[1]) ? $versionSignature[1] : 0, 69 | 'version_patch' => !empty($versionSignature[2]) ? $versionSignature[2] : 0, 70 | ]); 71 | 72 | if (!empty($sig[2])) { 73 | $r = preg_split('/([0-9]+)/', $sig[2], -1, PREG_SPLIT_DELIM_CAPTURE); 74 | if (is_array($r) && !empty($r)) { 75 | $package->set('release', $r[0]); 76 | $package->set('release_index', ($r[1] ?? '0')); 77 | } else { 78 | $package->set('release', $sig[2]); 79 | } 80 | } 81 | if ($package->save() && $package->install()) { 82 | return true; 83 | } else { 84 | Logger::error("Could not install package '{$name}'!"); 85 | } 86 | } 87 | } 88 | Logger::error("Could not find package '{$name}' in '{$providerName}' repository!"); 89 | } 90 | 91 | return false; 92 | } 93 | 94 | protected function downloadPackage(string $url, string $dst): bool 95 | { 96 | try { 97 | $file = false; 98 | if (ini_get('allow_url_fopen')) { 99 | $file = @file_get_contents($url); 100 | } elseif (function_exists('curl_init')) { 101 | $ch = curl_init(); 102 | curl_setopt($ch, CURLOPT_URL, $url); 103 | curl_setopt($ch, CURLOPT_HEADER, 0); 104 | curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1); 105 | curl_setopt($ch, CURLOPT_TIMEOUT, 180); 106 | $safeMode = @ini_get('safe_mode'); 107 | $openBasedir = @ini_get('open_basedir'); 108 | if (empty($safeMode) && empty($openBasedir)) { 109 | curl_setopt($ch, CURLOPT_FOLLOWLOCATION, 1); 110 | } 111 | $file = curl_exec($ch); 112 | curl_close($ch); 113 | } 114 | 115 | if ($file) { 116 | file_put_contents($dst, $file); 117 | return file_exists($dst); 118 | } 119 | } catch (\Exception $e) { 120 | Logger::error($e->getMessage()); 121 | } 122 | return false; 123 | } 124 | 125 | protected function addProvider(array $data): bool 126 | { 127 | $this->modx->error->reset(); 128 | /** @var \modProcessorResponse $response */ 129 | $response = $this->modx->runProcessor('workspace/providers/create', $data); 130 | if ($response->isError()) { 131 | Logger::error($response->getResponse()); 132 | return false; 133 | } 134 | return true; 135 | } 136 | 137 | protected function hasProvider(string $name): bool 138 | { 139 | $providers = $this->getTransportProviders(); 140 | return isset($providers[$name]); 141 | } 142 | 143 | protected function getTransportProviders(): array 144 | { 145 | if ($this->transportProviders !== null) { 146 | return $this->transportProviders; 147 | } 148 | 149 | $this->transportProviders = []; 150 | /** @var \modTransportProvider $provider */ 151 | $list = $this->modx->getCollection('transport.modTransportProvider'); 152 | foreach ($list as $provider) { 153 | $this->transportProviders[$provider->get('name')] = $provider; 154 | } 155 | return $this->transportProviders; 156 | } 157 | 158 | } -------------------------------------------------------------------------------- /docker/modx/tools/configurator/src/Utils/Logger.php: -------------------------------------------------------------------------------- 1 | 2 |
3 | [[!msCart]] 4 | [[!msOrder]] 5 | [[!msGetOrder]] 6 |
-------------------------------------------------------------------------------- /docker/modx/tools/configurator/storage/ms2/pages/category.tpl: -------------------------------------------------------------------------------- 1 |
2 | [[!pdoPage? 3 | &element=`msProducts` 4 | ]] 5 | [[!+page.nav]] 6 |
-------------------------------------------------------------------------------- /docker/modx/tools/configurator/storage/ms2/templates/cart.tpl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Prihod/docker-modx/4fa05bd11e002f3e867d46cfa59956c613a33d52/docker/modx/tools/configurator/storage/ms2/templates/cart.tpl -------------------------------------------------------------------------------- /docker/modx/tools/configurator/storage/ms2/templates/category.tpl: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Prihod/docker-modx/4fa05bd11e002f3e867d46cfa59956c613a33d52/docker/modx/tools/configurator/storage/ms2/templates/category.tpl -------------------------------------------------------------------------------- /docker/modx/tools/configurator/storage/ms2/templates/product.tpl: -------------------------------------------------------------------------------- 1 |
2 | [[$msProduct.content]] 3 |
-------------------------------------------------------------------------------- /docker/nginx/default.conf.template: -------------------------------------------------------------------------------- 1 | server { 2 | listen 80; 3 | listen 443 ssl; 4 | http2 on; 5 | server_name ${SERVER_NAME}; 6 | root ${ROOT_PATH}; 7 | index index.php index.html; 8 | 9 | # Compression 10 | gzip on; 11 | gzip_vary on; 12 | gzip_static on; 13 | gzip_comp_level 6; 14 | gzip_min_length 1024; 15 | gzip_buffers 128 4k; 16 | gzip_http_version 1.1; 17 | gzip_types text/css text/javascript text/js text/plain text/richtext text/shtml text/x-component text/x-java-source text/x-markdown text/x-script text/xml image/bmp image/svg+xml image/vnd.microsoft.icon image/x-icon font/otf font/ttf font/x-woff multipart/bag multipart/mixed application/eot application/font application/font-sfnt application/font-woff application/javascript application/javascript-binast application/json application/ld+json application/manifest+json application/opentype application/otf application/rss+xml application/ttf application/truetype application/vnd.api+json application/vnd.ms-fontobject application/wasm application/xhtml+xml application/xml application/xml+rss application/x-httpd-cgi application/x-javascript application/x-opentype application/x-otf application/x-perl application/x-protobuf application/x-ttf; 18 | gzip_proxied any; 19 | 20 | client_max_body_size 100M; 21 | 22 | ssl_certificate ${SSL_PATH}/server.crt; 23 | ssl_certificate_key ${SSL_PATH}/server.key; 24 | ssl_prefer_server_ciphers on; 25 | ssl_ciphers 'ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:AES256-GCM-SHA384'; 26 | ssl_protocols TLSv1.2 TLSv1.3; 27 | 28 | location = /favicon.ico { 29 | log_not_found off; 30 | access_log off; 31 | } 32 | 33 | location = /robots.txt { 34 | log_not_found off; 35 | access_log off; 36 | } 37 | 38 | location ~ /\.ht { 39 | deny all; 40 | } 41 | 42 | location ~ ^/config.core.php { 43 | return 404; 44 | } 45 | 46 | # Polylang language rewrite 47 | #location ~ "^/([a-z]{2})/" { 48 | # rewrite "^/([a-z]{2})/(.*)" /$2 last; 49 | #} 50 | 51 | location ~* ^.+\.(jpeg|jpg|png|webp|gif|bmp|ico|svg|css|js)$ { 52 | expires max; 53 | access_log off; 54 | log_not_found off; 55 | add_header Cache-Control "public, must-revalidate, proxy-revalidate"; 56 | } 57 | 58 | location / { 59 | try_files $uri $uri/ @rewrite; 60 | } 61 | 62 | location @rewrite { 63 | rewrite ^/(.*)$ /index.php?q=$1; 64 | } 65 | 66 | location ~ \.php$ { 67 | try_files $uri =404; 68 | fastcgi_split_path_info ^(.+\.php)(.*)$; 69 | include fastcgi_params; 70 | fastcgi_pass php:9000; 71 | fastcgi_index index.php; 72 | fastcgi_buffers 16 16k; 73 | fastcgi_buffer_size 32k; 74 | fastcgi_param SERVER_NAME $http_host; 75 | fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; 76 | fastcgi_param PATH_INFO $fastcgi_path_info; 77 | fastcgi_param PHP_VALUE "auto_prepend_file=/usr/local/php/xhprof/handler.php"; 78 | } 79 | } -------------------------------------------------------------------------------- /docker/nginx/ssl/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Prihod/docker-modx/4fa05bd11e002f3e867d46cfa59956c613a33d52/docker/nginx/ssl/.gitkeep -------------------------------------------------------------------------------- /docker/php/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG PHP_VERSION=7.4 2 | 3 | ARG LOCALE=en_US.UTF-8 4 | 5 | ARG SMTP_PORT=1025 6 | ARG SMTP_HOST=mailhog 7 | ARG ROOT_PATH=/var/www/html 8 | 9 | ARG XDEBUG_ENABLE=0 10 | ARG XHPROF_ENABLE=0 11 | ARG BLACKFIRE_ENABLE=0 12 | 13 | ARG SSH_ENABLE=0 14 | ARG SSH_LOGIN=dev 15 | ARG SSH_PASSWORD=dev 16 | 17 | FROM php:${PHP_VERSION}-fpm 18 | 19 | ARG LOCALE 20 | 21 | ARG SMTP_HOST 22 | ARG SMTP_PORT 23 | ARG ROOT_PATH 24 | 25 | ARG XDEBUG_ENABLE 26 | ARG XHPROF_ENABLE 27 | ARG BLACKFIRE_ENABLE 28 | 29 | ARG SSH_ENABLE 30 | ARG SSH_LOGIN 31 | ARG SSH_PASSWORD 32 | 33 | ENV ENTRYPOINT_PATH=/usr/local/bin 34 | ENV MODX_TOOLS_PATH=/usr/local/modx/tools 35 | ENV XHPROF_PATH=/usr/local/php/xhprof 36 | ENV MODX_STORAGE_PATH=/usr/local/modx/storage 37 | ENV MODX_BACKUP_PATH=/usr/local/modx/storage/backup 38 | ENV MODX_CACHE_SOURCE_PATH=/usr/local/modx/storage/cache 39 | 40 | RUN apt-get update && apt-get install -y --no-install-recommends \ 41 | libzip-dev \ 42 | libpng-dev \ 43 | libjpeg-dev \ 44 | libfreetype6-dev \ 45 | libmagickwand-dev \ 46 | libmagic-dev \ 47 | imagemagick \ 48 | pkg-config \ 49 | build-essential \ 50 | msmtp \ 51 | msmtp-mta \ 52 | mariadb-client \ 53 | git \ 54 | make \ 55 | zip \ 56 | unzip \ 57 | wget \ 58 | curl \ 59 | nano \ 60 | locales \ 61 | procps \ 62 | openssl \ 63 | openssh-server \ 64 | telnet \ 65 | && docker-php-ext-configure gd --with-freetype --with-jpeg \ 66 | && docker-php-ext-install pcntl gd mysqli pdo pdo_mysql opcache fileinfo zip \ 67 | && pecl install redis \ 68 | && git clone https://github.com/Imagick/imagick.git /usr/src/php/ext/imagick \ 69 | && cd /usr/src/php/ext/imagick \ 70 | && docker-php-ext-install imagick \ 71 | && docker-php-ext-enable imagick redis opcache \ 72 | && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* 73 | 74 | RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer 75 | 76 | RUN { \ 77 | echo "# msmtp configuration"; \ 78 | echo "defaults"; \ 79 | echo "auth off"; \ 80 | echo "tls off"; \ 81 | echo "host ${SMTP_HOST}"; \ 82 | echo "port ${SMTP_PORT}"; \ 83 | echo "logfile /proc/self/fd/2"; \ 84 | echo "account default"; \ 85 | echo "add_missing_date_header on"; \ 86 | echo "from mail@example.local"; \ 87 | } > /etc/msmtprc \ 88 | && chmod 644 /etc/msmtprc 89 | 90 | RUN echo "[mail function]\n" \ 91 | "SMTP=${SMTP_HOST}\n" \ 92 | "smtp_port=${SMTP_PORT}\n" \ 93 | "sendmail_path=/usr/bin/msmtp -t -i\n" \ 94 | > /usr/local/etc/php/conf.d/mailhog.ini 95 | 96 | RUN if [ -n "$LOCALE" ]; then \ 97 | echo "${LOCALE} UTF-8" >> /etc/locale.gen && \ 98 | locale-gen && \ 99 | echo "export LANG=${LOCALE}" >> /etc/profile.d/php_locale.sh && \ 100 | echo "export LC_ALL=${LOCALE}" >> /etc/profile.d/php_locale.sh; \ 101 | fi 102 | 103 | RUN if [ "${XDEBUG_ENABLE}" -eq 1 ]; then \ 104 | if [ "${PHP_VERSION}" \< "8.0" ]; then \ 105 | pecl install xdebug-2.9.8; \ 106 | else \ 107 | pecl install xdebug; \ 108 | fi && \ 109 | docker-php-ext-enable xdebug; \ 110 | fi 111 | 112 | COPY xhprof/ $XHPROF_PATH/ 113 | RUN if [ "${XHPROF_ENABLE}" -eq 1 ]; then \ 114 | pecl install mongodb xhprof \ 115 | && docker-php-ext-enable mongodb xhprof \ 116 | && cd $XHPROF_PATH \ 117 | && composer update \ 118 | && chown -R www-data:www-data $XHPROF_PATH \ 119 | && chmod -R 775 $XHPROF_PATH; \ 120 | fi 121 | 122 | RUN if [ "$BLACKFIRE_ENABLE" -eq 1 ]; then \ 123 | version=$(php -r "echo PHP_MAJOR_VERSION.PHP_MINOR_VERSION.(PHP_ZTS ? '-zts' : '');") \ 124 | && architecture=$(uname -m) \ 125 | && curl -A "Docker" -o /tmp/blackfire-probe.tar.gz -D - -L -s https://blackfire.io/api/v1/releases/probe/php/linux/$architecture/$version \ 126 | && mkdir -p /tmp/blackfire \ 127 | && tar zxpf /tmp/blackfire-probe.tar.gz -C /tmp/blackfire \ 128 | && mv /tmp/blackfire/blackfire-*.so $(php -r "echo ini_get ('extension_dir');")/blackfire.so \ 129 | && printf "extension=blackfire.so\nblackfire.agent_socket=tcp://blackfire:8307\n" > $PHP_INI_DIR/conf.d/blackfire.ini \ 130 | && rm -rf /tmp/blackfire /tmp/blackfire-probe.tar.gz; \ 131 | fi 132 | 133 | RUN if [ "$SSH_ENABLE" -eq 1 ]; then \ 134 | mkdir /var/run/sshd \ 135 | && sed -i 's/#PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config \ 136 | && useradd -rm -d ${ROOT_PATH} -s /bin/bash -g root -G www-data -u 1000 ${SSH_LOGIN} \ 137 | && echo "${SSH_LOGIN}:${SSH_PASSWORD}" | chpasswd; \ 138 | fi 139 | 140 | COPY sh/modx-*.sh $ENTRYPOINT_PATH/ 141 | RUN chmod 777 $ENTRYPOINT_PATH/modx-*.sh 142 | 143 | ENTRYPOINT ["/usr/local/bin/modx-docker-start.sh"] 144 | 145 | WORKDIR $ROOT_PATH -------------------------------------------------------------------------------- /docker/php/conf/opcache.ini: -------------------------------------------------------------------------------- 1 | opcache.memory_consumption = 128 2 | opcache.interned_strings_buffer = 8 3 | opcache.max_accelerated_files = 4000 4 | opcache.fast_shutdown = 1 5 | opcache.enable_cli = 0 -------------------------------------------------------------------------------- /docker/php/conf/php.ini: -------------------------------------------------------------------------------- 1 | log_errors=On 2 | display_errors=On 3 | error_log= /var/log/php-fpm/error.log 4 | error_reporting=E_ALL & ~E_NOTICE 5 | 6 | max_execution_time=120 7 | upload_max_filesize=100M 8 | upload_tmp_dir=/tmp -------------------------------------------------------------------------------- /docker/php/conf/xdebug.ini: -------------------------------------------------------------------------------- 1 | ; Xdebug 2.x: 2 | xdebug.remote_enable = 1 3 | xdebug.remote_autostart = 1 4 | xdebug.remote_port = 9000 5 | xdebug.remote_host = host.docker.internal 6 | 7 | ; Xdebug 3.x 8 | xdebug.mode = debug 9 | xdebug.client_host = host.docker.internal 10 | xdebug.start_start_with_request = yes 11 | xdebug.log_level = 0 -------------------------------------------------------------------------------- /docker/php/conf/xhprof.ini: -------------------------------------------------------------------------------- 1 | xhprof.collect_additional_info = 1 2 | xhprof.output_dir = /tmp/xhprof -------------------------------------------------------------------------------- /docker/php/sh/modx-clear-db.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "Start cleaning DB: ${MODX_DB_NAME}..." 4 | mysql -u"$MODX_DB_USER" -p"$MODX_DB_PASSWORD" -h"$MODX_DB_SERVER" -e " 5 | SET FOREIGN_KEY_CHECKS = 0; 6 | USE $MODX_DB_NAME; 7 | SET GROUP_CONCAT_MAX_LEN = 1000000; 8 | SET @tables = NULL; 9 | SELECT GROUP_CONCAT('\`', table_name, '\`') INTO @tables 10 | FROM information_schema.tables 11 | WHERE table_schema = '$MODX_DB_NAME'; 12 | SET @sql = IFNULL(CONCAT('DROP TABLE ', @tables), 'SELECT \"No tables to drop\"'); 13 | PREPARE stmt FROM @sql; 14 | EXECUTE stmt; 15 | DEALLOCATE PREPARE stmt; 16 | SET FOREIGN_KEY_CHECKS = 1; 17 | " || echo "Error when deleting tables from the database $MODX_DB_NAME" 18 | echo "Finish cleaning DB" 19 | -------------------------------------------------------------------------------- /docker/php/sh/modx-clear-site.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | echo "Start cleaning site directory: ${ROOT_PATH} ..." 4 | find "${ROOT_PATH}" -mindepth 1 -delete 5 | echo "Finish cleaning site directory" -------------------------------------------------------------------------------- /docker/php/sh/modx-configure.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | configurator_path="${MODX_CORE_PATH}configurator" 5 | 6 | echo "Copying files for Modx configurator..." 7 | mkdir -p "$configurator_path" 8 | cp -rf "${MODX_TOOLS_PATH}"/configurator/* "$configurator_path" 9 | 10 | echo "Set permissions for Modx configurator files..." 11 | chown -R www-data:www-data "$configurator_path" 12 | chmod -R 775 "$configurator_path" 13 | 14 | echo "Run composer update for Modx configurator..." 15 | cd "$configurator_path" 16 | composer update; 17 | 18 | php "$configurator_path"/run.php 19 | 20 | if [[ "$MODX_CONFIGURE_DEV_MODE" -eq 0 ]]; then 21 | rm -rf "$configurator_path" 22 | rm -rf "${MODX_CORE_PATH}cache/*" 23 | fi 24 | 25 | chown -R www-data:www-data "${ROOT_PATH}" 26 | chmod -R 775 "${ROOT_PATH}" 27 | 28 | 29 | -------------------------------------------------------------------------------- /docker/php/sh/modx-docker-start.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | if [ "$SSH_ENABLE" -eq 1 ]; then 4 | /usr/sbin/sshd 5 | fi 6 | 7 | bash "${ENTRYPOINT_PATH}/docker-php-entrypoint" 8 | 9 | if [[ "$SSL_GENERATE" -eq 1 && ! -f "${SSL_PATH}/server.crt" ]]; then 10 | bash "${ENTRYPOINT_PATH}/modx-generate-ssl.sh"; 11 | fi 12 | 13 | if [[ "$MODX_INSTALL_ENABLE" -eq 1 ]]; then 14 | 15 | version_file="${MODX_CORE_PATH}docs/version.inc.php" 16 | if [[ "$MODX_RESET" -eq 1 && -f "$version_file" ]]; then 17 | bash "${ENTRYPOINT_PATH}/modx-uninstall.sh"; 18 | fi 19 | 20 | if [[ "$MODX_IMPORT" != "0" ]]; then 21 | bash "${ENTRYPOINT_PATH}/modx-import.sh"; 22 | elif [ ! -f "$version_file" ]; then 23 | bash "${ENTRYPOINT_PATH}/modx-install.sh"; 24 | if [ "$MODX_CONFIGURE_ENABLE" -eq 1 ]; then 25 | bash "${ENTRYPOINT_PATH}/modx-configure.sh"; 26 | fi 27 | else 28 | echo "Starting check upgrade Modx version" 29 | installed_version=$(php -r "\$v = include '$version_file'; echo \$v['full_version'];") 30 | 31 | echo "Installed Modx version: $installed_version" 32 | echo "Required Modx version: $MODX_VERSION" 33 | 34 | is_less=$(php -r "echo version_compare('$installed_version','$MODX_VERSION', '<');") 35 | 36 | if [[ -n "$is_less" && "$is_less" -eq 1 ]]; then 37 | bash "${ENTRYPOINT_PATH}/modx-upgrade.sh"; 38 | else 39 | echo "No need to upgrade the Modx version" 40 | fi 41 | fi 42 | fi 43 | 44 | if ! pgrep php-fpm; then 45 | php-fpm 46 | fi -------------------------------------------------------------------------------- /docker/php/sh/modx-download.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | source_file="${MODX_CACHE_SOURCE_PATH}/modx-${MODX_VERSION}.zip" 5 | modx_download_url="https://modx.com/download/direct/modx-${MODX_VERSION}.zip" 6 | 7 | if [[ ! -f "$source_file" ]]; then 8 | echo "Downloading Modx ${MODX_VERSION} ..." 9 | echo "Download Modx URL ${modx_download_url}" 10 | curl -o "${MODX_CACHE_SOURCE_PATH}/modx-${MODX_VERSION}.zip" -L "${modx_download_url}" 11 | fi 12 | 13 | 14 | echo "Unzip Modx..." 15 | unzip -q "$source_file" -d "${ROOT_PATH}" 16 | 17 | echo "Moving extracted Modx files..." 18 | cp -rf "${ROOT_PATH}"/modx-"${MODX_VERSION}"/* "${ROOT_PATH}" 19 | #mv "${ROOT_PATH}"/modx-"${MODX_VERSION}"/* "${ROOT_PATH}" 20 | 21 | echo "Deleting temporary Modx files..." 22 | rm -rf "${ROOT_PATH}"/modx-"${MODX_VERSION}" 23 | 24 | if [[ "$MODX_USE_CACHE_SOURCE" -ne 1 && -f "$source_file" ]]; then 25 | rm "$source_file" 26 | fi 27 | 28 | echo "Set permissions Modx files..." 29 | chown -R www-data:www-data "${ROOT_PATH}" 30 | chmod -R 775 "${ROOT_PATH}" -------------------------------------------------------------------------------- /docker/php/sh/modx-export.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | echo "Start export Modx" 5 | 6 | temp_dir=$(mktemp -d) 7 | main_config=${MODX_CORE_PATH}config/config.inc.php 8 | 9 | if [ "$MODX_EXPORT_DB" -eq 1 ]; then 10 | echo "Export DB: ${MODX_DB_NAME}..." 11 | 12 | db_file="db.dump.sql" 13 | table_prefix=$(grep -Po '\$table_prefix\s*=\s*'\''.*?'\''' "$main_config" | perl -pe "s/.*'(.*?)'.*/\1/") 14 | table_prefix="${table_prefix:-$MODX_TABLE_PREFIX}" 15 | session_table="${table_prefix}session" 16 | 17 | echo "Cleaning table: ${session_table}..." 18 | mysql -u"$MODX_DB_USER" -p"$MODX_DB_PASSWORD" -h"$MODX_DB_SERVER" "$MODX_DB_NAME" -e "TRUNCATE TABLE ${session_table};" 19 | 20 | echo "Creating a dump: ${MODX_DB_NAME}..." 21 | mysqldump -u"$MODX_DB_USER" -p"$MODX_DB_PASSWORD" -h"$MODX_DB_SERVER" "$MODX_DB_NAME" > "$temp_dir"/"$db_file" 22 | fi 23 | 24 | if [ "$MODX_EXPORT_SITE" -eq 1 ]; then 25 | echo "Export site directory: ${ROOT_PATH}..." 26 | 27 | cd "$ROOT_PATH" 28 | configs=( 29 | "$main_config" 30 | "${ROOT_PATH}"/config.core.php 31 | "${MODX_CONTEXT_MANAGER_PATH}"config.core.php 32 | "${MODX_CONTEXT_CONNECTORS_PATH}"config.core.php 33 | ) 34 | 35 | echo "Cleaning Modx cache folder..." 36 | rm -rf "${MODX_CORE_PATH}"cache/* 37 | 38 | if [ "$MODX_EXPORT_OVERWRITE_CONFIG" -eq 1 ]; then 39 | echo "Creating a backup of Modx config files..." 40 | for file in "${configs[@]}" 41 | do 42 | if [ -f "$file" ]; then 43 | echo "Backuping file: ${file}.bak" 44 | cp "$file" "${file}.bak" 45 | fi 46 | done 47 | 48 | if [ -n "$MODX_EXPORT_DB_USER" ]; then 49 | sed -i "s|\(\$database_user\s*=\s*\)[\"'][^\"']*[\"'];|\1'${MODX_EXPORT_DB_USER}';|g" "$main_config" 50 | fi 51 | 52 | if [ -n "$MODX_EXPORT_DB_PASSWORD" ]; then 53 | sed -i "s|\(\$database_password\s*=\s*\)[\"'][^\"']*[\"'];|\1'${MODX_EXPORT_DB_PASSWORD}';|g" "$main_config" 54 | fi 55 | 56 | if [ -n "$MODX_EXPORT_DB_SERVER" ]; then 57 | sed -i "s|\(\$database_server\s*=\s*\)[\"'][^\"']*[\"'];|\1'${MODX_EXPORT_DB_SERVER}';|g" "$main_config" 58 | sed -i "s|\(\$database_dsn = '[^:]*:host=\)[^;]*|\1${MODX_EXPORT_DB_SERVER}|" "$main_config" 59 | fi 60 | 61 | if [ -n "$MODX_EXPORT_DB_NAME" ]; then 62 | sed -i "s|\(\$dbase\s*=\s*\)[\"'][^\"']*[\"'];|\1'${MODX_EXPORT_DB_NAME}';|g" "$main_config" 63 | sed -i "/^\$database_dsn =/s|\(dbname=\)[^;]*|\1${MODX_EXPORT_DB_NAME}|" "$main_config" 64 | fi 65 | 66 | if [ -n "$MODX_EXPORT_CORE_PATH" ]; then 67 | file=${ROOT_PATH}/config.core.php 68 | sed -i "s|^\(define('MODX_CORE_PATH', \).*);|\1'$MODX_EXPORT_CORE_PATH');|g" "$file" 69 | 70 | file=${MODX_CONNECTORS_PATH}config.core.php 71 | sed -i "s|^\(define('MODX_CORE_PATH', \).*);|\1'$MODX_EXPORT_CORE_PATH');|g" "$file" 72 | 73 | file=${MODX_MANAGER_PATH}config.core.php 74 | sed -i "s|^\(define('MODX_CORE_PATH', \).*);|\1'$MODX_EXPORT_CORE_PATH');|g" "$file" 75 | 76 | sed -i "s|\(\$modx_core_path\s*=\s*\)[\"'][^\"']*[\"'];|\1'${MODX_EXPORT_CORE_PATH}';|g" "$main_config" 77 | sed -i "s|\(\$modx_processors_path\s*=\s*\)[\"'][^\"']*[\"'];|\1'${MODX_EXPORT_CORE_PATH}model/modx/processors/';|g" "$main_config" 78 | fi 79 | 80 | if [ -n "$MODX_EXPORT_CONNECTORS_PATH" ]; then 81 | sed -i "s|\(\$modx_connectors_path\s*=\s*\)[\"'][^\"']*[\"'];|\1'${MODX_EXPORT_CONNECTORS_PATH}';|g" "$main_config" 82 | fi 83 | 84 | if [ -n "$MODX_EXPORT_CONNECTORS_URL" ]; then 85 | sed -i "s|\(\$modx_connectors_url\s*=\s*\)[\"'][^\"']*[\"'];|\1'${MODX_EXPORT_CONNECTORS_URL}';|g" "$main_config" 86 | fi 87 | 88 | if [ -n "$MODX_EXPORT_MANAGER_PATH" ]; then 89 | sed -i "s|\(\$modx_manager_path\s*=\s*\)[\"'][^\"']*[\"'];|\1'${MODX_EXPORT_MANAGER_PATH}';|g" "$main_config" 90 | fi 91 | 92 | if [ -n "$MODX_EXPORT_MANAGER_URL" ]; then 93 | sed -i "s|\(\$modx_manager_url\s*=\s*\)[\"'][^\"']*[\"'];|\1'${MODX_EXPORT_MANAGER_URL}';|g" "$main_config" 94 | fi 95 | 96 | if [ -n "$MODX_EXPORT_HTTP_HOST" ]; then 97 | sed -i "s|\(\$http_host\s*=\s*\)[\"'][^\"']*[\"'];|\1'${MODX_EXPORT_HTTP_HOST}';|g" "$main_config" 98 | sed -i "s|$MODX_HTTP_HOST|$MODX_EXPORT_HTTP_HOST|g" "$main_config" 99 | fi 100 | 101 | if [ -n "$MODX_EXPORT_BASE_PATH" ]; then 102 | sed -i "s|\(\$modx_base_path\s*=\s*\)[\"'][^\"']*[\"'];|\1'${MODX_EXPORT_BASE_PATH}';|g" "$main_config" 103 | sed -i "s|\(\$modx_assets_path\s*=\s*\)[\"'][^\"']*[\"'];|\1'${MODX_EXPORT_BASE_PATH}assets/';|g" "$main_config" 104 | fi 105 | 106 | if [ -n "$MODX_EXPORT_BASE_URL" ]; then 107 | sed -i "s|\(\$modx_base_url\s*=\s*\)[\"'][^\"']*[\"'];|\1'${MODX_EXPORT_BASE_URL}';|g" "$main_config" 108 | sed -i "s|\(\$modx_assets_url\s*=\s*\)[\"'][^\"']*[\"'];|\1'${MODX_EXPORT_BASE_URL}assets/';|g" "$main_config" 109 | fi 110 | 111 | if [ -n "$MODX_EXPORT_ASSETS_PATH" ]; then 112 | sed -i "s|\(\$modx_assets_path\s*=\s*\)[\"'][^\"']*[\"'];|\1'${MODX_EXPORT_ASSETS_PATH}';|g" "$main_config" 113 | fi 114 | 115 | if [ -n "$MODX_EXPORT_ASSETS_URL" ]; then 116 | sed -i "s|\(\$modx_assets_url\s*=\s*\)[\"'][^\"']*[\"'];|\1'${MODX_EXPORT_ASSETS_URL}';|g" "$main_config" 117 | fi 118 | fi 119 | 120 | archive_site="site.zip"; 121 | echo "Compress site dir into: ${archive_site}" 122 | zip -q -r "$archive_site" ./ -x "./*.php.bak" 123 | 124 | if [ "$MODX_EXPORT_OVERWRITE_CONFIG" -eq 1 ]; then 125 | for file in "${configs[@]}" 126 | do 127 | file_bak="${file}.bak" 128 | if [ -f "$file_bak" ]; then 129 | mv "$file_bak" "$file" 130 | fi 131 | done 132 | fi 133 | 134 | echo "Moving ${archive_site} to ${temp_dir}" 135 | mv "$archive_site" "$temp_dir" 136 | fi 137 | 138 | if [ "$(ls -A "$temp_dir")" ]; then 139 | cd "$temp_dir" 140 | date=$(date +\%F_\%T) 141 | export_file="${date}.zip" 142 | 143 | echo "Compress all export into: ${export_file}" 144 | zip -r "$export_file" ./ 145 | 146 | echo "Moving ${export_file} to ${MODX_BACKUP_PATH}" 147 | mv "$export_file" "$MODX_BACKUP_PATH" 148 | fi 149 | 150 | echo "Finish export Modx" -------------------------------------------------------------------------------- /docker/php/sh/modx-generate-ssl.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | mkdir -p "$SSL_PATH" 5 | 6 | echo "Start generate SSL into ${SSL_PATH}" 7 | openssl req -x509 -nodes -days 365 \ 8 | -newkey rsa:2048 \ 9 | -keyout "$SSL_PATH"/server.key \ 10 | -out "$SSL_PATH"/server.crt \ 11 | -subj "/CN=${SSL_HOST:-$MODX_HTTP_HOST}"; -------------------------------------------------------------------------------- /docker/php/sh/modx-import.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | echo "Start Import Modx" 5 | 6 | if [ "$MODX_IMPORT" == "latest" ]; then 7 | archive_file=$(find "$MODX_BACKUP_PATH" -name "*.zip" -type f -printf '%T@ %p\n' | sort -nr | head -n 1 | cut -d' ' -f2-) 8 | else 9 | if [[ "$MODX_IMPORT" =~ \.zip$ ]]; then 10 | archive_file="${MODX_BACKUP_PATH}/${MODX_IMPORT}" 11 | else 12 | archive_file="${MODX_BACKUP_PATH}/${MODX_IMPORT}.zip" 13 | fi 14 | fi 15 | 16 | if [[ -z "$archive_file" || ! -f "$archive_file" ]]; then 17 | echo "Import zip file not found in directory: ${MODX_BACKUP_PATH}" 18 | exit 1 19 | fi 20 | 21 | temp_dir=$(mktemp -d) 22 | cd "$temp_dir" 23 | 24 | echo "Copy ${archive_file} to ${temp_dir}" 25 | cp "$archive_file" "$temp_dir" 26 | 27 | echo "Unzip ${archive_file}" 28 | unzip "$archive_file" 29 | 30 | if [[ "$MODX_IMPORT_SITE" -eq 1 ]]; then 31 | site_zip_file="${temp_dir}/site.zip" 32 | if [[ -f "$site_zip_file" ]]; then 33 | echo "Start site import from file: ${site_zip_file}" 34 | bash "${ENTRYPOINT_PATH}/modx-clear-site.sh"; 35 | 36 | echo "Unzip ${site_zip_file}..." 37 | unzip -q "$site_zip_file" -d "$ROOT_PATH" 38 | 39 | echo "Finish site import" 40 | else 41 | echo "Site import zip file: ${site_zip_file} not found!" 42 | fi 43 | fi 44 | 45 | if [[ "$MODX_IMPORT_DB" -eq 1 ]]; then 46 | db_file="${temp_dir}/db.dump.sql" 47 | if [[ -f "$db_file" ]]; then 48 | bash "${ENTRYPOINT_PATH}/modx-clear-db.sh"; 49 | echo "Start DB import from dump file: ${db_file}" 50 | mysql -u"$MODX_DB_USER" -p"$MODX_DB_PASSWORD" -h"$MODX_DB_SERVER" "$MODX_DB_NAME" < "$db_file" 51 | echo "Finish DB import" 52 | else 53 | echo "DB import dump file: ${db_file} not found!" 54 | fi 55 | fi 56 | 57 | echo "Finish Import Modx" -------------------------------------------------------------------------------- /docker/php/sh/modx-install.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | prepare_table_prefix() { 4 | input=$1 5 | if [[ $input =~ ^random:([0-9]+)$ ]]; then 6 | length=${BASH_REMATCH[1]} 7 | random_string=$(head /dev/urandom | tr -dc 'A-Za-z0-9' | head -c "$length") 8 | echo "${random_string}_" 9 | else 10 | echo "$input" 11 | fi 12 | } 13 | 14 | bash "${ENTRYPOINT_PATH}/modx-download.sh" 15 | 16 | echo "Install MODX preparing..." 17 | install_config=$(cat "$ROOT_PATH/setup/config.dist.new.xml") 18 | 19 | table_prefix=$(prepare_table_prefix "$MODX_TABLE_PREFIX") 20 | install_config="${install_config//modx_/$table_prefix}" 21 | echo " Param table_prefix: $table_prefix" 22 | 23 | install_config=$(echo $install_config | sed "s/localhost/$MODX_DB_SERVER/g") 24 | install_config="${install_config//localhost/$MODX_DB_SERVER}" 25 | echo " Param database_server: $MODX_DB_SERVER" 26 | 27 | install_config="${install_config//modx_modx/$MODX_DB_NAME}" 28 | echo " Param database: $MODX_DB_NAME" 29 | 30 | install_config="${install_config//db_username/$MODX_DB_USER}" 31 | echo " Param database_user: $MODX_DB_USER" 32 | 33 | install_config="${install_config//db_password/$MODX_DB_PASSWORD}" 34 | echo " Param database_password: $MODX_DB_PASSWORD" 35 | 36 | install_config="${install_config//utf8/$MODX_DB_CONNECTION_CHARSET}" 37 | echo " Param database_connection_charset: $MODX_DB_CONNECTION_CHARSET" 38 | 39 | install_config="${install_config//utf8/$MODX_DB_CHARSET}" 40 | echo " Param database_charset: $MODX_DB_CHARSET" 41 | 42 | install_config="${install_config//utf8_general_ci/$MODX_DB_CHARSET_COLLATION}" 43 | echo " Param database_collation: $MODX_DB_CHARSET_COLLATION" 44 | 45 | install_config="${install_config//443/$MODX_HTTPS_PORT}" 46 | echo " Param https_port: $MODX_HTTPS_PORT" 47 | 48 | install_config="${install_config//localhost/$MODX_HTTP_HOST}" 49 | echo " Param http_host: $MODX_HTTP_HOST" 50 | 51 | install_config="${install_config//0/1}" 52 | echo " Param inplace: 1" 53 | 54 | install_config="${install_config//0/1}" 55 | echo " Param unpacked: 1" 56 | 57 | install_config="${install_config//en/$MODX_LANGUAGE}" 58 | echo " Param: language :$MODX_LANGUAGE" 59 | 60 | install_config="${install_config//username/$MODX_CMS_ADMIN}" 61 | echo " Param cmsadmin: $MODX_CMS_ADMIN" 62 | 63 | install_config="${install_config//password/$MODX_CMS_PASS}" 64 | echo " Param cmspassword: $MODX_CMS_PASS" 65 | 66 | install_config="${install_config//email@address.com/$MODX_CMS_EMAIL}" 67 | echo " Param cmsadminemail: $MODX_CMS_EMAIL" 68 | 69 | install_config="${install_config//1/$MODX_REMOVE_SETUP_DIRECTORY}" 70 | echo " Param remove_setup_directory: $MODX_REMOVE_SETUP_DIRECTORY" 71 | 72 | install_config="${install_config//\/www\/modx\/core\//$MODX_CORE_PATH}" 73 | echo " Param core_path: $MODX_CORE_PATH" 74 | 75 | install_config="${install_config//\/www\/modx\/connectors\//$MODX_CONNECTORS_PATH}" 76 | echo " Param context_connectors_path: $MODX_CONNECTORS_PATH" 77 | 78 | install_config="${install_config//\/modx\/connectors\//$MODX_CONNECTORS_URL}" 79 | echo " Param: context_connectors_url: $MODX_CONNECTORS_URL" 80 | 81 | install_config="${install_config//\/www\/modx\/manager\//$MODX_MANAGER_PATH}" 82 | echo " Param context_mgr_path: $MODX_MANAGER_PATH" 83 | 84 | install_config="${install_config//\/modx\/manager\//$MODX_MANAGER_URL}" 85 | echo " Param context_mgr_url: $MODX_MANAGER_URL" 86 | 87 | install_config="${install_config//\/www\/modx\//$MODX_BASE_PATH}" 88 | echo " Param context_web_path: $MODX_BASE_PATH" 89 | 90 | install_config="${install_config//\/modx\//$MODX_BASE_URL}" 91 | echo " Param context_web_url: $MODX_BASE_URL" 92 | 93 | echo "$install_config" > "$ROOT_PATH"/setup/config.xml 94 | 95 | echo "Start installation Modx..." 96 | php "$ROOT_PATH"/setup/index.php --installmode=new 97 | 98 | echo "Modx installation is complete!" -------------------------------------------------------------------------------- /docker/php/sh/modx-uninstall.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | echo "Start uninstall Modx" 5 | bash "${ENTRYPOINT_PATH}/modx-clear-site.sh"; 6 | bash "${ENTRYPOINT_PATH}/modx-clear-db.sh"; 7 | echo "Finish uninstall Modx" -------------------------------------------------------------------------------- /docker/php/sh/modx-upgrade.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | set -e 3 | 4 | bash "${ENTRYPOINT_PATH}/modx-download.sh" 5 | 6 | echo "Upgrade MODX preparing..." 7 | upgrade_config=$(cat "$ROOT_PATH/setup/config.dist.upgrade.xml") 8 | 9 | upgrade_config="${upgrade_config//0/1}" 10 | echo " Param inplace: 1" 11 | 12 | upgrade_config="${upgrade_config//0/0}" 13 | echo " Param unpacked: 0" 14 | 15 | upgrade_config="${upgrade_config//en/$MODX_LANGUAGE}" 16 | echo " Param: language :$MODX_LANGUAGE" 17 | 18 | echo "$upgrade_config" > "$ROOT_PATH"/setup/config.xml 19 | 20 | echo "Start of modx upgrade..." 21 | php "$ROOT_PATH"/setup/index.php --installmode=upgrade 22 | 23 | echo "Modx upgrade is complete!" 24 | -------------------------------------------------------------------------------- /docker/php/xhprof/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "require": { 3 | "mongodb/mongodb": "^1.20.0" 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /docker/php/xhprof/handler.php: -------------------------------------------------------------------------------- 1 | selectCollection(getenv('XHGUI_MONGO_DATABASE'), 'results'); 10 | $data = array( 11 | 'meta' => array( 12 | 'url' => $_SERVER['REQUEST_URI'], 13 | 'get' => $_GET, 14 | 'SERVER' => $_SERVER, 15 | // 'env' => $_ENV, 16 | 'simple_url' => parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH), 17 | 'request_ts' => new MongoDB\BSON\UTCDateTime(microtime(true) * 1000), 18 | 'request_date' => date('Y-m-d'), 19 | ), 20 | 'profile' => $xhprof_data 21 | ); 22 | $collection->insertOne($data); 23 | }); 24 | } -------------------------------------------------------------------------------- /docker/volume/mariadb/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Prihod/docker-modx/4fa05bd11e002f3e867d46cfa59956c613a33d52/docker/volume/mariadb/.gitkeep -------------------------------------------------------------------------------- /docker/xhgui/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM php:8.1-apache 2 | 3 | RUN apt-get update && apt-get install -y --no-install-recommends \ 4 | git \ 5 | unzip \ 6 | libmongoc-1.0-0 \ 7 | libssl-dev \ 8 | && pecl install mongodb \ 9 | && docker-php-ext-enable mongodb \ 10 | && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* 11 | 12 | RUN echo "date.timezone=UTC" > /usr/local/etc/php/conf.d/timezone.ini 13 | 14 | RUN echo "error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT" > /usr/local/etc/php/conf.d/error-reporting.ini 15 | 16 | RUN curl -sS https://getcomposer.org/installer | php -- --install-dir=/usr/local/bin --filename=composer 17 | 18 | WORKDIR /var/www/html 19 | 20 | RUN git clone https://github.com/perftools/xhgui.git . \ 21 | && chmod -R 0777 cache \ 22 | && composer install --no-dev 23 | 24 | 25 | RUN a2enmod rewrite 26 | COPY apache.conf /etc/apache2/sites-available/000-default.conf 27 | 28 | RUN chown -R www-data:www-data /var/www/html \ 29 | && chmod -R 755 /var/www/html 30 | 31 | COPY config.php /var/www/html/config/config.default.php 32 | 33 | COPY templates/runs/view.twig /var/www/html/templates/custom/runs/view.twig -------------------------------------------------------------------------------- /docker/xhgui/apache.conf: -------------------------------------------------------------------------------- 1 | 2 | ServerAdmin webmaster@localhost 3 | DocumentRoot /var/www/html/webroot 4 | 5 | 6 | Options Indexes FollowSymLinks 7 | AllowOverride All 8 | Require all granted 9 | 10 | 11 | ErrorLog ${APACHE_LOG_DIR}/error.log 12 | CustomLog ${APACHE_LOG_DIR}/access.log combined 13 | -------------------------------------------------------------------------------- /docker/xhgui/config.php: -------------------------------------------------------------------------------- 1 | 'mongodb', 13 | 14 | 'mongodb' => [ 15 | // 'hostname' and 'port' are used to build DSN for MongoClient 16 | 'hostname' => getenv('XHGUI_MONGO_HOSTNAME') ?: '127.0.0.1', 17 | 'port' => getenv('XHGUI_MONGO_PORT') ?: 27017, 18 | // The database name 19 | 'database' => getenv('XHGUI_MONGO_DATABASE') ?: 'xhprof', 20 | // Additional options for the MongoClient constructor, 21 | // for example 'username', 'password', or 'replicaSet'. 22 | // See . 23 | 'options' => [ 24 | /* 25 | 'username' => getenv('XHGUI_MONGO_USERNAME') ?: null, 26 | 'password' => getenv('XHGUI_MONGO_PASSWORD') ?: null, 27 | */ 28 | ], 29 | // An array of options for the MongoDB driver. 30 | // Options include setting connection context options for SSL or logging callbacks. 31 | // See . 32 | 'driverOptions' => [], 33 | ], 34 | 35 | 'run.view.filter.names' => [ 36 | 'Zend*', 37 | 'Composer*', 38 | ], 39 | 40 | // If defined, add imports via upload (/run/import) must pass token parameter with this value 41 | 'upload.token' => getenv('XHGUI_UPLOAD_TOKEN') ?: '', 42 | 43 | // Add this path prefix to all links and resources 44 | // If this is not defined, auto-detection will try to find it itself 45 | // Example: 46 | // - prefix=null: use auto-detection from request 47 | // - prefix='': use '' for prefix 48 | // - prefix='/xhgui': use '/xhgui' 49 | 'path.prefix' => null, 50 | 51 | // Setup timezone for date formatting 52 | // Example: 'UTC', 'Europe/Tallinn' 53 | // If left empty, php default will be used (php.ini or compiled in default) 54 | 'timezone' => '', 55 | 56 | // Date format used when browsing XHGui pages. 57 | // 58 | // Must be a format supported by the PHP date() function. 59 | // See . 60 | 'date.format' => 'M jS H:i:s', 61 | 62 | // The number of items to show in "Top lists" with functions 63 | // using the most time or memory resources, on XHGui Run pages. 64 | 'detail.count' => 6, 65 | 66 | // The number of items to show per page, on XHGui list pages. 67 | 'page.limit' => 25, 68 | 69 | 'template_dir' => [ 70 | '/var/www/html/templates/custom', 71 | '/var/www/html/templates', 72 | ], 73 | ]; 74 | -------------------------------------------------------------------------------- /docker/xhgui/mongo.init.d/xhgui.js: -------------------------------------------------------------------------------- 1 | db.results.ensureIndex( { 'meta.SERVER.REQUEST_TIME' : -1 } ); 2 | db.results.ensureIndex( { 'profile.main().wt' : -1 } ); 3 | db.results.ensureIndex( { 'profile.main().mu' : -1 } ); 4 | db.results.ensureIndex( { 'profile.main().cpu' : -1 } ); 5 | db.results.ensureIndex( { 'meta.url' : 1 } ); 6 | db.results.ensureIndex( { 'meta.SERVER.SERVER_NAME' : 1 } ); -------------------------------------------------------------------------------- /docker/xhgui/templates/runs/view.twig: -------------------------------------------------------------------------------- 1 | {% extends 'layout/base.twig' %} 2 | {% import 'macros/helpers.twig' as helpers %} 3 | 4 | {% block title %} 5 | - Profile - {{ result.meta.url }} 6 | {% endblock %} 7 | 8 | {% block content %} 9 |
10 |

Profile data for {{ result.meta('SERVER.REQUEST_METHOD')}} {{ result.meta.SERVER.SERVER_NAME }}{{ result.meta.url }}

11 |
12 |
13 |
14 | 40 |
41 | 42 |
43 | 44 |
45 | 46 | Compare this run 47 | 48 | 49 | View Callgraph 50 | 51 | 52 | Jump to functions 53 | 54 | 55 |

Watch Functions

56 | 57 | 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | {% for value in watches %} 69 | 70 | 78 | 79 | 80 | 81 | 82 | 83 | {% else %} 84 | 88 | {% endfor %} 89 | 90 |
FunctionCall Countewtemuepmu
71 | {{ helpers.tip_link( 72 | value.function, 73 | 65, 74 | 'run.symbol', 75 | {'id': result.id|trim, 'symbol': value.function} 76 | ) }} 77 | {{ value.ct }}{{ value.ewt|as_time }}{{ value.emu|as_bytes }}{{ value.epmu|as_bytes }}
85 | You have no watch functions setup. 86 | Add a watch function now. 87 |
91 |
92 | 93 |
94 |
95 |

Self Wall Time

96 |
97 |
98 | {% for value in wall_time %} 99 |
{{ value.name }}
100 |
{{ value.value|as_time }}
101 | {% endfor %} 102 |
103 |
104 | 105 |
106 |

Memory Hogs

107 |
108 |
109 | {% for value in memory %} 110 |
{{ value.name }}
111 |
{{ value.value|as_bytes }}
112 | {% endfor %} 113 |
114 |
115 |
116 |
117 |
118 | 119 |
120 | 123 |
124 | 125 |
126 | 127 | 128 | 129 | 130 | 131 | 132 | 133 | 134 | 135 | 136 | 137 | 138 | 139 | 140 | 141 | 142 | 143 | {% for key, value in profile %} 144 | 145 | 153 | 154 | 155 | 156 | 157 | 158 | 159 | 160 | 161 | 162 | 163 | {% endfor %} 164 | 165 |
FunctionCall CountSelf Wall TimeSelf CPUSelf Memory UsageSelf Peak Memory UsageInclusive Wall TimeInclusive CPUInclusive Memory UsageInclusive Peak Memory Usage
146 | {{ helpers.tip_link( 147 | key, 148 | 65, 149 | 'run.symbol', 150 | {'id': result.id|trim, 'symbol': key} 151 | ) }} 152 | {{ value.ct }}{{ value.ewt|as_time }}{{ value.ecpu|as_time }}{{ value.emu|as_bytes }}{{ value.epmu|as_bytes }}{{ value.wt|as_time }}{{ value.cpu|as_time }}{{ value.mu|as_bytes }}{{ value.pmu|as_bytes }}
166 |
167 | {% endblock %} 168 | 169 | {% block jsfoot %} 170 | 200 | {% endblock %} -------------------------------------------------------------------------------- /docs/images/video-cover.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Prihod/docker-modx/4fa05bd11e002f3e867d46cfa59956c613a33d52/docs/images/video-cover.jpg -------------------------------------------------------------------------------- /www/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Prihod/docker-modx/4fa05bd11e002f3e867d46cfa59956c613a33d52/www/.gitkeep --------------------------------------------------------------------------------