├── .ddev ├── apache │ ├── 00-vhost-macro.conf │ ├── 10-index.conf │ ├── 30-v10.conf │ └── 40-v11.conf ├── commands │ └── web │ │ ├── cache-clear │ │ ├── cache-flush-all │ │ ├── dbdump │ │ ├── install-all │ │ ├── install-v10 │ │ └── install-v11 ├── config.yaml ├── data-init │ ├── database_v10.sql │ ├── database_v11.sql │ ├── fileadmin │ │ └── user_upload │ │ │ ├── mountains.gif │ │ │ ├── mountains.jpg │ │ │ └── mountains.png │ └── index.html ├── docker-compose.web.yaml ├── homeadditions │ └── .config │ │ └── mc │ │ └── ini └── web-build │ └── Dockerfile ├── .gitignore ├── .scrutinizer.yml ├── .styleci.yml ├── .travis.yml ├── CHANGELOG.rst ├── Classes ├── Command │ ├── BaseCommand.php │ ├── OptimizeFalProcessedImages.php │ ├── OptimizeFolderImages.php │ ├── ResetOptimizationFlagForFal.php │ └── ResetOptimizationFlagForFolders.php ├── Configuration │ └── Configurator.php ├── Domain │ ├── Model │ │ ├── AbstractBaseResult.php │ │ ├── ExecutorResult.php │ │ ├── ModeResult.php │ │ ├── ProviderResult.php │ │ └── StepResult.php │ └── Repository │ │ ├── ExecutorResultRepository.php │ │ ├── ModeResultRepository.php │ │ ├── ProviderResultRepository.php │ │ └── StepResultRepository.php ├── Executor │ ├── OptimizationExecutorBase.php │ ├── OptimizationExecutorInterface.php │ ├── OptimizationExecutorRemote.php │ ├── OptimizationExecutorRemoteImageoptim.php │ ├── OptimizationExecutorRemoteKraken.php │ ├── OptimizationExecutorRemoteTinypng.php │ └── OptimizationExecutorShell.php ├── Provider │ └── OptimizationProvider.php ├── Resource │ ├── PageRepository.php │ └── ProcessedFileRepository.php ├── Service │ ├── OptimizeImageService.php │ ├── OptimizeImagesFalService.php │ └── OptimizeImagesFolderService.php ├── Utility │ ├── ArrayUtility.php │ ├── CliDisplayUtility.php │ ├── FrontendProcessingUtility.php │ └── TemporaryFileUtility.php └── Xclass │ ├── ContentObjectRenderer.php │ └── FileProcessingService.php ├── Configuration ├── Services.yaml ├── TCA │ ├── Overrides │ │ ├── pages.php │ │ └── sys_templates.php │ ├── tx_imageopt_domain_model_executorresult.php │ ├── tx_imageopt_domain_model_moderesult.php │ ├── tx_imageopt_domain_model_providerresult.php │ └── tx_imageopt_domain_model_stepresult.php ├── TsConfig │ └── Page │ │ ├── tx_imageopt.tsconfig │ │ ├── tx_imageopt__0100.tsconfig │ │ ├── tx_imageopt__0110.tsconfig │ │ ├── tx_imageopt__0120.tsconfig │ │ ├── tx_imageopt__0200.tsconfig │ │ ├── tx_imageopt__0210.tsconfig │ │ ├── tx_imageopt__0220.tsconfig │ │ └── tx_imageopt__0230.tsconfig └── TypoScript │ └── setup.typoscript ├── Documentation └── Images │ ├── ExecutorResultExample.png │ └── OutputCliExample.png ├── LICENSE.txt ├── README.rst ├── Resources ├── Private │ ├── .htaccess │ ├── Cert │ │ └── cacert.pem │ └── Language │ │ ├── locallang.xlf │ │ ├── locallang_csh_tx_imageopt_domain_model_executorresult.xlf │ │ ├── locallang_csh_tx_imageopt_domain_model_optimizationoptionresult.xlf │ │ ├── locallang_csh_tx_imageopt_domain_model_optimizationstepresult.xlf │ │ ├── locallang_csh_tx_imageopt_domain_model_providerresult.xlf │ │ └── locallang_db.xlf └── Public │ └── Icons │ ├── Extension.svg │ ├── relation.gif │ ├── tx_imageopt_domain_model_executorresult.gif │ ├── tx_imageopt_domain_model_optimizationoptionresult.gif │ ├── tx_imageopt_domain_model_optimizationstepresult.gif │ └── tx_imageopt_domain_model_providerresult.gif ├── Tests ├── Fixture │ └── Unit │ │ └── OptimizeImageService │ │ ├── mountains.gif │ │ ├── mountains.jpg │ │ └── mountains.png └── Unit │ ├── Configuration │ └── ConfiguratorTest.php │ └── Service │ └── OptimizeImageServiceTest.php ├── composer.json ├── ext_emconf.php ├── ext_localconf.php ├── ext_tables.php └── ext_tables.sql /.ddev/apache/00-vhost-macro.conf: -------------------------------------------------------------------------------- 1 | 2 | 3 | ServerName $domain 4 | DocumentRoot $path 5 | 6 | AllowOverride All 7 | Allow from All 8 | 9 | 10 | RewriteEngine On 11 | RewriteCond %{HTTP:X-Forwarded-Proto} =https 12 | RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} -d 13 | RewriteRule ^(.+[^/])$ https://%{HTTP_HOST}$1/ [redirect,last] 14 | SetEnvIf X-Forwarded-Proto "https" HTTPS=on 15 | ErrorLog /dev/stdout 16 | CustomLog ${APACHE_LOG_DIR}/access.log combined 17 | Alias "/phpstatus" "/var/www/phpstatus.php" 18 | 19 | 20 | 21 | ServerName $domain 22 | DocumentRoot $path 23 | 24 | AllowOverride All 25 | Allow from All 26 | 27 | 28 | RewriteEngine On 29 | RewriteCond %{HTTP:X-Forwarded-Proto} =https 30 | RewriteCond %{DOCUMENT_ROOT}%{REQUEST_FILENAME} -d 31 | RewriteRule ^(.+[^/])$ https://%{HTTP_HOST}$1/ [redirect,last] 32 | SetEnvIf X-Forwarded-Proto "https" HTTPS=on 33 | ErrorLog /dev/stdout 34 | CustomLog ${APACHE_LOG_DIR}/access.log combined 35 | Alias "/phpstatus" "/var/www/phpstatus.php" 36 | 37 | SSLEngine on 38 | SSLCertificateFile /etc/ssl/certs/master.crt 39 | SSLCertificateKeyFile /etc/ssl/certs/master.key 40 | 41 | -------------------------------------------------------------------------------- /.ddev/apache/10-index.conf: -------------------------------------------------------------------------------- 1 | Use vhost-macro imageopt.ddev.site /var/www/html/.test -------------------------------------------------------------------------------- /.ddev/apache/30-v10.conf: -------------------------------------------------------------------------------- 1 | Use vhost-macro v10.imageopt.ddev.site /var/www/html/.test/v10/public 2 | -------------------------------------------------------------------------------- /.ddev/apache/40-v11.conf: -------------------------------------------------------------------------------- 1 | Use vhost-macro v11.imageopt.ddev.site /var/www/html/.test/v11/public -------------------------------------------------------------------------------- /.ddev/commands/web/cache-clear: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | /usr/bin/php /var/www/html/v10/vendor/bin/typo3cms cache:flush 4 | /usr/bin/php /var/www/html/v11/vendor/bin/typo3cms cache:flush 5 | -------------------------------------------------------------------------------- /.ddev/commands/web/cache-flush-all: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ## Description: Flush cache for imageopt test instances 4 | ## Usage: cache-flush-all 5 | ## Example: "ddev cache-flush-all" 6 | 7 | /usr/bin/php /var/www/html/.test/v10/vendor/bin/typo3cms cache:flush 8 | /usr/bin/php /var/www/html/.test/v11/vendor/bin/typo3cms cache:flush 9 | -------------------------------------------------------------------------------- /.ddev/commands/web/dbdump: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ## Description: Dump v10/v11 database to data-init folder. 4 | ## Usage: dbdump v10|v11 5 | ## Example: "ddev dbdump v11" 6 | 7 | if [[ ${@} = "" ]]; then echo "Set either v10 or v11 as argument." && exit 1; fi 8 | 9 | DATABASE=database_$@ 10 | echo 'TRUNCATE sys_history; TRUNCATE sys_log; TRUNCATE be_sessions; TRUNCATE fe_sessions; TRUNCATE fe_sessions; TRUNCATE sys_file_processedfile; TRUNCATE sys_lockedrecords; TRUNCATE tx_extensionmanager_domain_model_extension;' | mysql -uroot -proot $DATABASE 11 | echo "SELECT concat('TRUNCATE TABLE ', TABLE_NAME, ';') FROM INFORMATION_SCHEMA.TABLES WHERE TABLE_SCHEMA = '$DATABASE' AND TABLE_NAME LIKE 'cache%'" | mysql -uroot -proot $DATABASE | sed 1d > tmp.sql 12 | cat tmp.sql | mysql -uroot -proot $DATABASE 13 | rm tmp.sql 14 | 15 | mysqldump --skip-triggers --extended-insert --single-transaction --skip-add-locks --skip-disable-keys --quick --skip-comments -uroot -proot -hdb $DATABASE > /var/www/html/.ddev/data-init/$DATABASE.sql 16 | echo "Dump finished" -------------------------------------------------------------------------------- /.ddev/commands/web/install-all: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ## Description: Install TYPO3 10 and TYPO3 11 with imageopt linked to it. 4 | ## Usage: install-all 5 | ## Example: "ddev install-all" 6 | 7 | ABSOLUTE_PATH="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/$(basename "${BASH_SOURCE[0]}/.")" 8 | 9 | $ABSOLUTE_PATH/install-v10 10 | $ABSOLUTE_PATH/install-v11 11 | -------------------------------------------------------------------------------- /.ddev/commands/web/install-v10: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ## Description: Install TYPO3 10 with project extension. 4 | ## Usage: install-v10 5 | ## Example: "ddev install-v10" 6 | 7 | VERSION=v10 8 | DATABASE=database_$VERSION 9 | BASE_PATH=/var/www/html/.test/$VERSION 10 | 11 | [ -L "/var/www/html/.test/${EXTENSION_KEY}" ] || ln -sr /var/www/html/ "/var/www/html/.test/${EXTENSION_KEY}" 12 | rm -rf $BASE_PATH 13 | mkdir -p $BASE_PATH 14 | 15 | mysql -uroot -proot -e "DROP DATABASE IF EXISTS $DATABASE" 16 | 17 | composer init --name=sourcebroker/typo3$VERSION --description=TYPO3$VERSION --no-interaction --working-dir $BASE_PATH 18 | composer config --no-plugins allow-plugins.typo3/cms-composer-installers true -d $BASE_PATH 19 | composer config --no-plugins allow-plugins.typo3/class-alias-loader true -d $BASE_PATH 20 | 21 | composer config repositories.$EXTENSION_KEY path ../$EXTENSION_KEY --working-dir $BASE_PATH 22 | composer req typo3/minimal:'^10.4' typo3/cms-tstemplate:'^10.4' typo3/cms-fluid-styled-content:'^10.4' typo3/cms-recycler:'^10.4' helhum/typo3-console:'^6.3' sourcebroker/$EXTENSION_KEY:'@dev' \ 23 | --no-progress --no-interaction --working-dir $BASE_PATH 24 | 25 | $BASE_PATH/vendor/bin/typo3cms install:setup -n --database-name $DATABASE 26 | $BASE_PATH/vendor/bin/typo3cms configuration:set 'BE/debug' 1 27 | $BASE_PATH/vendor/bin/typo3cms configuration:set 'BE/lockSSL' true 28 | $BASE_PATH/vendor/bin/typo3cms configuration:set 'FE/debug' 1 29 | $BASE_PATH/vendor/bin/typo3cms configuration:set 'SYS/devIPmask' '*' 30 | $BASE_PATH/vendor/bin/typo3cms configuration:set 'SYS/displayErrors' 1 31 | $BASE_PATH/vendor/bin/typo3cms configuration:set 'SYS/trustedHostsPattern' '.*.*' 32 | $BASE_PATH/vendor/bin/typo3cms configuration:set 'MAIL/transport' 'smtp' 33 | $BASE_PATH/vendor/bin/typo3cms configuration:set 'MAIL/transport_smtp_server' 'localhost:1025' 34 | $BASE_PATH/vendor/bin/typo3cms configuration:set 'GFX/processor' 'ImageMagick' 35 | $BASE_PATH/vendor/bin/typo3cms configuration:set 'GFX/processor_path' '/usr/bin/' 36 | $BASE_PATH/vendor/bin/typo3cms configuration:set 'GFX/processor_path_lzw' '/usr/bin/' 37 | $BASE_PATH/vendor/bin/typo3cms install:generatepackagestates 38 | 39 | sed -i -e "s/base: ht\//base: \//g" $BASE_PATH/config/sites/main/config.yaml 40 | sed -i -e 's/base: \/en\//base: \//g' $BASE_PATH/config/sites/main/config.yaml 41 | printf "imports:\n -\n resource: 'EXT:$EXTENSION_KEY/Configuration/Routing/config.yaml'" >> $BASE_PATH/config/sites/main/config.yaml 42 | 43 | cp -r "$BASE_PATH/../$EXTENSION_KEY/.ddev/data-init/fileadmin/" "$BASE_PATH/public/" 44 | mysql -uroot -proot $DATABASE < "$BASE_PATH/../$EXTENSION_KEY/.ddev/data-init/$DATABASE.sql" 45 | 46 | $BASE_PATH/vendor/bin/typo3cms database:updateschema 47 | $BASE_PATH/vendor/bin/typo3cms cache:flush 48 | -------------------------------------------------------------------------------- /.ddev/commands/web/install-v11: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | ## Description: Install TYPO3 11 with project extension. 4 | ## Usage: install-v11 5 | ## Example: "ddev install-v11" 6 | 7 | VERSION=v11 8 | DATABASE=database_$VERSION 9 | BASE_PATH=/var/www/html/.test/$VERSION 10 | 11 | [ -L "/var/www/html/.test/${EXTENSION_KEY}" ] || ln -sr /var/www/html/ "/var/www/html/.test/${EXTENSION_KEY}" 12 | rm -rf $BASE_PATH 13 | mkdir -p $BASE_PATH 14 | 15 | mysql -uroot -proot -e "DROP DATABASE IF EXISTS $DATABASE" 16 | 17 | composer init --name=sourcebroker/typo3$VERSION --description=TYPO3$VERSION --no-interaction --working-dir $BASE_PATH 18 | composer config --no-plugins allow-plugins.typo3/cms-composer-installers true -d $BASE_PATH 19 | composer config --no-plugins allow-plugins.typo3/class-alias-loader true -d $BASE_PATH 20 | 21 | composer config repositories.$EXTENSION_KEY path ../$EXTENSION_KEY --working-dir $BASE_PATH 22 | composer req typo3/minimal:'^11.5' typo3/cms-tstemplate:'^11.5' typo3/cms-fluid-styled-content:'^11.5' typo3/cms-recycler:'^11.5' helhum/typo3-console:'^7.1.2' \ sourcebroker/$EXTENSION_KEY:'@dev' \ 23 | --no-progress --no-interaction --working-dir $BASE_PATH 24 | 25 | $BASE_PATH/vendor/bin/typo3cms install:setup -n --database-name $DATABASE 26 | $BASE_PATH/vendor/bin/typo3cms configuration:set 'BE/debug' 1 27 | $BASE_PATH/vendor/bin/typo3cms configuration:set 'BE/lockSSL' true 28 | $BASE_PATH/vendor/bin/typo3cms configuration:set 'FE/debug' 1 29 | $BASE_PATH/vendor/bin/typo3cms configuration:set 'SYS/devIPmask' '*' 30 | $BASE_PATH/vendor/bin/typo3cms configuration:set 'SYS/displayErrors' 1 31 | $BASE_PATH/vendor/bin/typo3cms configuration:set 'SYS/trustedHostsPattern' '.*.*' 32 | $BASE_PATH/vendor/bin/typo3cms configuration:set 'MAIL/transport' 'smtp' 33 | $BASE_PATH/vendor/bin/typo3cms configuration:set 'MAIL/transport_smtp_server' 'localhost:1025' 34 | $BASE_PATH/vendor/bin/typo3cms configuration:set 'GFX/processor' 'ImageMagick' 35 | $BASE_PATH/vendor/bin/typo3cms configuration:set 'GFX/processor_path' '/usr/bin/' 36 | $BASE_PATH/vendor/bin/typo3cms configuration:set 'GFX/processor_path_lzw' '/usr/bin/' 37 | 38 | sed -i -e "s/base: ht\//base: \//g" $BASE_PATH/config/sites/main/config.yaml 39 | sed -i -e 's/base: \/en\//base: \//g' $BASE_PATH/config/sites/main/config.yaml 40 | printf "imports:\n -\n resource: 'EXT:$EXTENSION_KEY/Configuration/Routing/config.yaml'" >> $BASE_PATH/config/sites/main/config.yaml 41 | 42 | cp -r "$BASE_PATH/../$EXTENSION_KEY/.ddev/data-init/fileadmin/" "$BASE_PATH/public/" 43 | mysql -uroot -proot $DATABASE < "$BASE_PATH/../$EXTENSION_KEY/.ddev/data-init/$DATABASE.sql" 44 | 45 | $BASE_PATH/vendor/bin/typo3cms database:updateschema 46 | $BASE_PATH/vendor/bin/typo3cms cache:flush 47 | -------------------------------------------------------------------------------- /.ddev/config.yaml: -------------------------------------------------------------------------------- 1 | name: imageopt 2 | type: php 3 | docroot: .test 4 | php_version: "7.4" 5 | composer_version: 2 6 | webserver_type: apache-fpm 7 | router_http_port: "80" 8 | router_https_port: "443" 9 | xdebug_enabled: false 10 | additional_hostnames: 11 | - v10.imageopt 12 | - v11.imageopt 13 | additional_fqdns: [] 14 | provider: default 15 | use_dns_when_possible: true 16 | hooks: 17 | post-start: 18 | - exec: sudo sed -i '1s/^/TERM=xterm-256color\n/' ~/.bashrc 19 | - exec: mkdir -p .test && cp /var/www/html/.ddev/data-init/index.html /var/www/html/.test/ 20 | 21 | -------------------------------------------------------------------------------- /.ddev/data-init/fileadmin/user_upload/mountains.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourcebroker/imageopt/281bd2f463b4c3b95b9fb07b926a5f36adf502b1/.ddev/data-init/fileadmin/user_upload/mountains.gif -------------------------------------------------------------------------------- /.ddev/data-init/fileadmin/user_upload/mountains.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourcebroker/imageopt/281bd2f463b4c3b95b9fb07b926a5f36adf502b1/.ddev/data-init/fileadmin/user_upload/mountains.jpg -------------------------------------------------------------------------------- /.ddev/data-init/fileadmin/user_upload/mountains.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourcebroker/imageopt/281bd2f463b4c3b95b9fb07b926a5f36adf502b1/.ddev/data-init/fileadmin/user_upload/mountains.png -------------------------------------------------------------------------------- /.ddev/data-init/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | imageopt 6 | 7 | 8 |

Run 'ddev install-all' to install TYPO3 10 and TYPO3 11 with imageopt linked to this source.

9 | 13 | 14 | -------------------------------------------------------------------------------- /.ddev/docker-compose.web.yaml: -------------------------------------------------------------------------------- 1 | version: "3.6" 2 | services: 3 | web: 4 | environment: 5 | - EXTENSION_KEY=imageopt 6 | - PACKAGE_NAME=sourcebroker/imageopt 7 | 8 | - TYPO3_INSTALL_DB_DRIVER=mysqli 9 | - TYPO3_INSTALL_DB_USER=root 10 | - TYPO3_INSTALL_DB_PASSWORD=root 11 | - TYPO3_INSTALL_DB_HOST=db 12 | - TYPO3_INSTALL_DB_PORT=3306 13 | - TYPO3_INSTALL_DB_UNIX_SOCKET= 14 | - TYPO3_INSTALL_DB_USE_EXISTING=0 15 | - TYPO3_INSTALL_ADMIN_USER=admin 16 | - TYPO3_INSTALL_ADMIN_PASSWORD=password 17 | - TYPO3_INSTALL_SITE_NAME=EXT:imageopt Dev Environment 18 | - TYPO3_INSTALL_SITE_SETUP_TYPE=site 19 | - TYPO3_INSTALL_WEB_SERVER_CONFIG=apache 20 | 21 | -------------------------------------------------------------------------------- /.ddev/homeadditions/.config/mc/ini: -------------------------------------------------------------------------------- 1 | [Midnight-Commander] 2 | verbose=true 3 | shell_patterns=true 4 | auto_save_setup=true 5 | preallocate_space=false 6 | auto_menu=false 7 | use_internal_view=true 8 | use_internal_edit=false 9 | clear_before_exec=true 10 | confirm_delete=true 11 | confirm_overwrite=true 12 | confirm_execute=false 13 | confirm_history_cleanup=true 14 | confirm_exit=false 15 | confirm_directory_hotlist_delete=false 16 | confirm_view_dir=false 17 | safe_delete=false 18 | safe_overwrite=false 19 | use_8th_bit_as_meta=false 20 | mouse_move_pages_viewer=true 21 | mouse_close_dialog=false 22 | fast_refresh=false 23 | drop_menus=false 24 | wrap_mode=true 25 | old_esc_mode=true 26 | cd_symlinks=true 27 | show_all_if_ambiguous=false 28 | use_file_to_guess_type=true 29 | alternate_plus_minus=false 30 | only_leading_plus_minus=true 31 | show_output_starts_shell=false 32 | xtree_mode=false 33 | file_op_compute_totals=true 34 | classic_progressbar=true 35 | use_netrc=true 36 | ftpfs_always_use_proxy=false 37 | ftpfs_use_passive_connections=true 38 | ftpfs_use_passive_connections_over_proxy=false 39 | ftpfs_use_unix_list_options=true 40 | ftpfs_first_cd_then_ls=true 41 | ignore_ftp_chattr_errors=true 42 | editor_fill_tabs_with_spaces=false 43 | editor_return_does_auto_indent=false 44 | editor_backspace_through_tabs=false 45 | editor_fake_half_tabs=true 46 | editor_option_save_position=true 47 | editor_option_auto_para_formatting=false 48 | editor_option_typewriter_wrap=false 49 | editor_edit_confirm_save=true 50 | editor_syntax_highlighting=true 51 | editor_persistent_selections=true 52 | editor_drop_selection_on_copy=true 53 | editor_cursor_beyond_eol=false 54 | editor_cursor_after_inserted_block=false 55 | editor_visible_tabs=true 56 | editor_visible_spaces=true 57 | editor_line_state=false 58 | editor_simple_statusbar=false 59 | editor_check_new_line=false 60 | editor_show_right_margin=false 61 | editor_group_undo=true 62 | editor_state_full_filename=true 63 | editor_ask_filename_before_edit=false 64 | nice_rotating_dash=true 65 | mcview_remember_file_position=false 66 | auto_fill_mkdir_name=true 67 | copymove_persistent_attr=true 68 | pause_after_run=1 69 | mouse_repeat_rate=100 70 | double_click_speed=250 71 | old_esc_mode_timeout=1000000 72 | max_dirt_limit=10 73 | num_history_items_recorded=60 74 | vfs_timeout=60 75 | ftpfs_directory_timeout=900 76 | ftpfs_retry_seconds=30 77 | fish_directory_timeout=900 78 | editor_tab_spacing=8 79 | editor_word_wrap_line_length=72 80 | editor_option_save_mode=0 81 | editor_backup_extension=~ 82 | editor_filesize_threshold=64M 83 | editor_stop_format_chars=-+*\\,.;:&> 84 | mcview_eof= 85 | skin=modarin256root-thin 86 | 87 | filepos_max_saved_entries=1024 88 | 89 | [Layout] 90 | message_visible=1 91 | keybar_visible=1 92 | xterm_title=1 93 | output_lines=0 94 | command_prompt=1 95 | menubar_visible=1 96 | free_space=1 97 | horizontal_split=0 98 | vertical_equal=1 99 | left_panel_size=156 100 | horizontal_equal=1 101 | top_panel_size=1 102 | 103 | [Misc] 104 | timeformat_recent=%b %e %H:%M 105 | timeformat_old=%b %e %Y 106 | ftp_proxy_host=gate 107 | ftpfs_password=anonymous@ 108 | display_codepage=ASCII 109 | source_codepage=Other_8_bit 110 | autodetect_codeset= 111 | spell_language=en 112 | clipboard_store= 113 | clipboard_paste= 114 | 115 | [Colors] 116 | base_color= 117 | xterm-256color= 118 | color_terminals= 119 | 120 | [Panels] 121 | show_mini_info=true 122 | kilobyte_si=false 123 | mix_all_files=false 124 | show_backups=true 125 | show_dot_files=true 126 | fast_reload=false 127 | fast_reload_msg_shown=false 128 | mark_moves_down=true 129 | reverse_files_only=true 130 | auto_save_setup_panels=false 131 | navigate_with_arrows=false 132 | panel_scroll_pages=true 133 | panel_scroll_center=false 134 | mouse_move_pages=true 135 | filetype_mode=true 136 | permission_mode=false 137 | torben_fj_mode=false 138 | quick_search_mode=2 139 | select_flags=6 140 | 141 | [Panelize] 142 | Find *.orig after patching=find . -name \\*.orig -print 143 | Find SUID and SGID programs=find . \\( \\( -perm -04000 -a -perm /011 \\) -o \\( -perm -02000 -a -perm /01 \\) \\) -print 144 | Find rejects after patching=find . -name \\*.rej -print 145 | Modified git files=git ls-files --modified 146 | -------------------------------------------------------------------------------- /.ddev/web-build/Dockerfile: -------------------------------------------------------------------------------- 1 | ARG BASE_IMAGE 2 | FROM $BASE_IMAGE 3 | 4 | RUN wget https://packages.sury.org/php/apt.gpg -O /etc/apt/trusted.gpg.d/php-sury.gpg 5 | 6 | RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install \ 7 | -y -o Dpkg::Options::="--force-confold" --no-install-recommends --no-install-suggests \ 8 | parallel \ 9 | jpegoptim \ 10 | optipng \ 11 | pngcrush \ 12 | gifsicle \ 13 | pngquant \ 14 | libjpeg-turbo-progs \ 15 | libperl6-slurp-perl \ 16 | libfile-slurp-perl 17 | 18 | RUN apt-get update && DEBIAN_FRONTEND=noninteractive apt-get install \ 19 | -y -o Dpkg::Options::="--force-confold" --no-install-recommends --no-install-suggests \ 20 | build-essential cmake libtool libwebp-dev unzip python-dev autoconf automake m4 nasm pkg-config \ 21 | libpng-dev libmagickcore-dev libmagickwand-dev pngnq libtool unzip 22 | 23 | RUN cd ~ && \ 24 | wget https://github.com/mozilla/mozjpeg/archive/v3.3.1.tar.gz && \ 25 | tar xf v3.3.1.tar.gz && \ 26 | cd mozjpeg-3.3.1 && \ 27 | autoreconf -fiv && \ 28 | mkdir build && \ 29 | cd build && \ 30 | sh ../configure --disable-shared --enable-static --prefix=/usr/local && \ 31 | sudo make install && \ 32 | sudo ln -s /usr/local/bin/cjpeg /usr/bin/mozjpeg-cjpeg && \ 33 | sudo ln -s /usr/local/bin/jpegtran /usr/bin/mozjpeg-jpegtran 34 | 35 | RUN a2enmod macro -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /.Build 2 | /composer.lock 3 | /var 4 | /.env 5 | /.test 6 | 7 | -------------------------------------------------------------------------------- /.scrutinizer.yml: -------------------------------------------------------------------------------- 1 | checks: 2 | php: 3 | code_rating: true 4 | -------------------------------------------------------------------------------- /.styleci.yml: -------------------------------------------------------------------------------- 1 | preset: psr2 2 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | dist: xenial 2 | sudo: false 3 | 4 | language: php 5 | 6 | matrix: 7 | fast_finish: true 8 | include: 9 | - php: 7.2 10 | env: TYPO3_VERSION=^9 11 | - php: 7.3 12 | env: TYPO3_VERSION=^9 13 | - php: 7.4 14 | env: TYPO3_VERSION=^9 15 | - php: 7.2 16 | env: TYPO3_VERSION=^10 17 | - php: 7.3 18 | env: TYPO3_VERSION=^10 19 | - php: 7.4 20 | env: TYPO3_VERSION=^10 21 | 22 | install: 23 | - export PROJECT_DIR=$PWD 24 | - sudo apt-get update -y 25 | - sudo apt-get -y install build-essential cmake libtool libwebp-dev unzip python-dev autoconf automake m4 nasm pkg-config libpng-dev libmagickcore-dev libmagickwand-dev pngnq 26 | - pip install setuptools 27 | # intall mozjpeg 28 | - cd ~ 29 | - wget https://github.com/mozilla/mozjpeg/archive/v3.3.1.tar.gz 30 | - tar xf v3.3.1.tar.gz 31 | - cd mozjpeg-3.3.1 32 | - autoreconf -fiv 33 | - mkdir build 34 | - cd build 35 | - sh ../configure --disable-shared --enable-static --prefix=/usr/local 36 | - sudo make install 37 | - sudo ln -s /usr/local/bin/cjpeg /usr/bin/mozjpeg-cjpeg 38 | - sudo ln -s /usr/local/bin/jpegtran /usr/bin/mozjpeg-jpegtran 39 | - cd $PROJECT_DIR 40 | 41 | addons: 42 | apt: 43 | packages: 44 | - parallel 45 | - jpegoptim 46 | - optipng 47 | - pngcrush 48 | - gifsicle 49 | - pngquant 50 | # jpgrescan 51 | - libjpeg-turbo-progs 52 | - libperl6-slurp-perl 53 | - libfile-slurp-perl 54 | cache: 55 | directories: 56 | - $HOME/.composer/cache 57 | 58 | before_install: 59 | - composer self-update 60 | - composer --version 61 | 62 | before_script: 63 | - pwd 64 | - cd $PROJECT_DIR 65 | - pwd 66 | - phpenv config-rm xdebug.ini 67 | - composer require typo3/minimal=$TYPO3_VERSION 68 | # Restore composer.json 69 | - git checkout composer.json 70 | - export TYPO3_PATH_WEB=$PWD/.Build/Web 71 | 72 | script: 73 | - > 74 | .Build/bin/phpunit --colors -c .Build/vendor/nimut/testing-framework/res/Configuration/UnitTests.xml Tests/Unit/ 75 | -------------------------------------------------------------------------------- /CHANGELOG.rst: -------------------------------------------------------------------------------- 1 | 2 | Changelog 3 | --------- 4 | 5 | 7.0.2 6 | ~~~~~ 7 | 8 | 1) [BUGFIX] Cleanup TCA from deprecated showRecordFieldList. 9 | 10 | 7.0.1 11 | ~~~~~ 12 | 13 | 1) [BUGFIX] Move addStaticFile from ext_tables to TCA/Overrides for sys_template. Replace TYPO3_MODE to TYPO3. 14 | 15 | 7.0.0 16 | ~~~~~ 17 | 18 | 1) [TASK] TYPO3 11 compatibility. 19 | 2) [BUGFIX] Fix failing when step has no provider. 20 | 3) [TASK] Extended ddev testbed. 21 | 4) [TASK] Cleanup ext_tables.sql from standard fields / rename TS setup extension 22 | 5) [BUGFIX] Fix $file in isAllowedToForceFrontendImageProcessing is not always a path (string). 23 | 6) [FEATURE] Allow to choose what files extensions are to be optimised. 24 | 7) [TASK] Remove wrap for database queries. Was used when yet TYPO3 below 8.7 was supported. 25 | 8) [BUGFIX][BREAKING] Add gif as supported extension for webp imagemagick provider. 26 | 9) [FEATURE] Add new default settings. Only webp optimisation. 27 | 10) [BUGFIX] Normalise size_before, size_after database schema to varchar. The best would be 28 | "int(11) unsigned DEFAULT NULL", but TYPO3 database update schema command do not accept this. 29 | 30 | 6.0.3 31 | ~~~~~ 32 | 33 | 1) [BUGFIX] Make composer.json valid. 34 | 35 | 6.0.2 36 | ~~~~~ 37 | 38 | 1) [BUGFIX] Fix removing of temporary filenames. 39 | 2) [TASK] Add composer allow-plugins. 40 | 41 | 6.0.1 42 | ~~~~~ 43 | 44 | 1) [BUGFIX] Make ext_tables.sql compatible with compare database tool. 45 | 46 | 6.0.0 47 | ~~~~~ 48 | 49 | 1) [TASK][!!!BREAKING] Replace CommandController commands with Symfony commands. 50 | 2) [BUGFIX] Fix wrong TS config for tx_imageopt__0110.tsconfig 51 | 3) [TASK] Drop support for TYPO3 7.6 and 8.7. Add support for TYPO3 10. 52 | 4) [TASK] Optimise travis config file. 53 | 54 | 5.0.0 55 | ~~~~~ 56 | 57 | 1) [FEATURE] Support for executorsDefault and providersDefault. 58 | 2) [TASK][BREAKING] Remove executor.enabled option. 59 | 3) [FEATURE] Add remote executors implementation (kraken / imageoptim / tinypng). 60 | 4) [FEATURE][BREAKING] Disable all providers by default. 61 | 5) [TASK] Add support for TYPO3 7.6 and PHP 5.6. 62 | 6) [FEATURE] Add .env files for unit test passwords for remote executors. 63 | 7) [FEATURE] Add type for providers and config override by type. 64 | 8) [FEATURE] Add new way to decide what images should be optimized: mix of provider type and regexp on filepath and filename. 65 | 9) [FEATURE] Add support for mozjpeg. 66 | 10) [TASK][BREAKING] Remove int key based quality as its hard to compare qualities. 67 | 11) [BUGFIX] Fix results not being persisted. 68 | 12) [FEATURE][!!!BREAKING] Add support for chained provides executors. 69 | 13) [TASK] Rename models / fix TCA relations. 70 | 14) [TASK] Set name and description for mode and step for better CLI reporting. 71 | 15) [TASK] Update FAL with file outside file storage. 72 | 16) [TASK] Make cli results more descriptive and easy to understand. 73 | 17) [FEATURE] Add configuration sets choosable in page properties 74 | 75 | 4.0.0 76 | ~~~~~ 77 | 78 | 1) [FEATURE][BREAKING] So far imageopt was forcing all images to be resized on fronted by default. Right now its 79 | configurable in Typoscript. To activate this behaviour you need to include static extension Typoscript 80 | in frontent template record. 81 | 2) [FEATURE] Allow to define files that should not be forced to be processed (regexp on filepath / filename). 82 | 3) [FEATURE] Add symfony commands and rework configurator. 83 | 4) [TASK] Add scheduler task as fallback for using symfony commands as scheduler task in TYPO3 8.7. 84 | 5) [TASK] Refactor for FileProcessingService xclass. 85 | 6) [TASK] Increase numberOfImagesToProcess from 20 to 50. 86 | 7) [TASK][BREAKING] Convert symfony argumnets to options. 87 | 88 | 3.0.0 89 | ~~~~~ 90 | 91 | 1) [TASK] Drop travis testing for PHP 5.6 and TYPO3 7.6. 92 | 2) [TASK] TYPO3 9.5 compatibility. 93 | 3) [TASK] ext_localconf.php refactor 94 | 4) [TASK] Drop travis testing for TYPO3 7.6. Add testing for TYPO3 9.5. 95 | 5) [TASK] Increase nimut/testing-framework for TYPO3 9.5 tests 96 | 6) [TASK] Update test for TYPO3 9.5 97 | 7) [TASK] Remove not used variables, improve phpdocs, cast variables. 98 | 99 | 2.0.1 100 | ~~~~~ 101 | 102 | 1) [BUGFIX] Correctly cleanup temp files. 103 | 104 | 2.0.0 105 | ~~~~~ 106 | 107 | 1) [DOC] Add missing changelog for version 1.2.1 108 | 2) [BUGFIX] Add missing "info" lang label 109 | 3) [FEATURE] Add wordwrap 70 for info when showing resulats on CLI 110 | 4) [BUGFIX] Do not throw exception if processed files is deleted - show info instead. 111 | 5) [TASK] Increase ext version ext_emconf.php 112 | 6) [DOC] Improve changelog. 113 | 7) [BREAKING] Replace function "exif_imagetype" with "getimagesize" which is more popular. 114 | 8) [DOCS] Improve docs / add overview images. 115 | 9) [TASK] Change typo3/cms to typo3/cms-core in composer json req. 116 | 117 | 1.2.1 118 | ~~~~~ 119 | 120 | 1) [BUGFIX] Increase ext version ext_emconf.php 121 | 122 | 1.2.0 123 | ~~~~~ 124 | 125 | 1) [FEATURE] Add support for choosing uid of page to parse TSConfig. If not set then fallback to first root page. 126 | 2) [FEATURE] Colapse 1:n relation of executorsResults in ProviderResult 127 | 128 | 1.1.0 129 | ~~~~~ 130 | 131 | 1) [BUGFIX] Fix wrong default value for file_relative_path / text. 132 | 2) [TASK] Optimize TCA settings for models. 133 | 134 | 1.0.2 135 | ~~~~~ 136 | 137 | 1) [BUGFIX] Fix wrong data type/size on sql. Fix Tests to reflect changed data types. 138 | 139 | 1.0.1 140 | ~~~~~ 141 | 142 | 1) [BUGFIX] Change composer.json description. 143 | 144 | 1.0.0 145 | ~~~~~ 146 | 147 | 1) [TASK][BREAKING] Remove services. 148 | 2) [TASK][BREAKING] Remove support for remote optimizers for now. It will be back later. 149 | 3) [TASK]Add support for chained executors. 150 | 4) [TASK][BREAKING] Remove services. 151 | 5) [TASK]Add models for OptimizationResult / ProviderResult / ExecutorResult. 152 | 6) [TASK][BREAKING] Modify TSconfig structure. 153 | 7) [TASK][BREAKING] Rename tx_imageopt_optimized to tx_imageopt_executed_successfully on sys_file_processedfile 154 | -------------------------------------------------------------------------------- /Classes/Command/BaseCommand.php: -------------------------------------------------------------------------------- 1 | setDescription('Optimize FAL processed images') 33 | ->addOption( 34 | 'numberOfImagesToProcess', 35 | null, 36 | InputOption::VALUE_REQUIRED, 37 | 'The number of images to process on single task call.' 38 | ) 39 | ->addOption( 40 | 'rootPageForTsConfig', 41 | null, 42 | InputOption::VALUE_REQUIRED, 43 | 'The page uid for which the TSconfig is parsed. If not set then first found root page will be used.' 44 | ); 45 | } 46 | 47 | /** 48 | * @param InputInterface $input 49 | * @param OutputInterface $output 50 | * @return int 51 | * @throws \InvalidArgumentException 52 | * @throws \Exception 53 | */ 54 | protected function execute(InputInterface $input, OutputInterface $output) 55 | { 56 | $io = new SymfonyStyle($input, $output); 57 | $io->title($this->getDescription()); 58 | 59 | $numberOfImagesToProcess = $input->hasOption('numberOfImagesToProcess') && $input->getOption('numberOfImagesToProcess') !== null 60 | ? $input->getOption('numberOfImagesToProcess') 61 | : 50; 62 | $rootPageForTsConfig = $input->hasOption('rootPageForTsConfig') && $input->getOption('rootPageForTsConfig') !== null 63 | ? $input->getOption('rootPageForTsConfig') 64 | : null; 65 | 66 | $objectManager = GeneralUtility::makeInstance(ObjectManager::class); 67 | 68 | $configurator = GeneralUtility::makeInstance(Configurator::class); 69 | $configurator->setConfigByPage($rootPageForTsConfig); 70 | $configurator->init(); 71 | 72 | /** @var OptimizeImagesFalService $optimizeImagesFalService */ 73 | $optimizeImagesFalService = $objectManager->get(OptimizeImagesFalService::class, $configurator->getConfig()); 74 | $extensions = GeneralUtility::trimExplode(',', $configurator->getOption('extensions'), true); 75 | $filesToProcess = $optimizeImagesFalService->getFalProcessedFilesToOptimize($numberOfImagesToProcess, $extensions); 76 | if (!empty($filesToProcess)) { 77 | foreach ($filesToProcess as $fileToProcess) { 78 | $optimizationResults = $optimizeImagesFalService->optimizeFalProcessedFile($fileToProcess); 79 | foreach ($optimizationResults as $optimizationResult) { 80 | $io->write(CliDisplayUtility::displayOptionResult($optimizationResult, $configurator->getConfig())); 81 | } 82 | } 83 | } else { 84 | if (!$io->isQuiet()) { 85 | $io->writeln('No images found that can be optimized.'); 86 | } 87 | } 88 | return 0; 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /Classes/Command/OptimizeFolderImages.php: -------------------------------------------------------------------------------- 1 | setDescription('Optimize images in folders') 33 | ->addOption( 34 | 'numberOfImagesToProcess', 35 | null, 36 | InputOption::VALUE_REQUIRED, 37 | 'The number of images to process on single task call.' 38 | ) 39 | ->addOption( 40 | 'rootPageForTsConfig', 41 | null, 42 | InputOption::VALUE_REQUIRED, 43 | 'The page uid for which the TSconfig is parsed. If not set then first found root page will be used.' 44 | ); 45 | } 46 | 47 | /** 48 | * @param InputInterface $input 49 | * @param OutputInterface $output 50 | * @return int 51 | * @throws \InvalidArgumentException 52 | * @throws \Exception 53 | */ 54 | protected function execute(InputInterface $input, OutputInterface $output) 55 | { 56 | $io = new SymfonyStyle($input, $output); 57 | $io->title($this->getDescription()); 58 | 59 | $numberOfImagesToProcess = $input->hasOption('numberOfImagesToProcess') && $input->getOption('numberOfImagesToProcess') !== null ? $input->getOption('numberOfImagesToProcess') : 50; 60 | $rootPageForTsConfig = $input->hasOption('rootPageForTsConfig') && $input->getOption('rootPageForTsConfig') !== null ? $input->getOption('rootPageForTsConfig') : null; 61 | 62 | $objectManager = GeneralUtility::makeInstance(ObjectManager::class); 63 | 64 | $configurator = GeneralUtility::makeInstance(Configurator::class); 65 | $configurator->setConfigByPage($rootPageForTsConfig); 66 | $configurator->init(); 67 | 68 | /** @var OptimizeImagesFolderService $optimizeImagesFolderService */ 69 | $optimizeImagesFolderService = $objectManager->get(OptimizeImagesFolderService::class, $configurator->getConfig()); 70 | 71 | $filesToProcess = $optimizeImagesFolderService->getFilesToOptimize($numberOfImagesToProcess); 72 | if (!empty($filesToProcess)) { 73 | foreach ($filesToProcess as $fileToProcess) { 74 | $optimizationResults = $optimizeImagesFolderService->optimizeFolderFile($fileToProcess); 75 | foreach ($optimizationResults as $optimizationResult) { 76 | $io->write(CliDisplayUtility::displayOptionResult($optimizationResult, $configurator->getConfig())); 77 | } 78 | } 79 | } else { 80 | if (!$io->isQuiet()) { 81 | $output->writeln('No images found that can be optimized.'); 82 | } 83 | } 84 | return 0; 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /Classes/Command/ResetOptimizationFlagForFal.php: -------------------------------------------------------------------------------- 1 | setDescription('Reset optimized flag for FAL processed images so all files can be optimized once more') 32 | ->addOption( 33 | 'rootPageForTsConfig', 34 | null, 35 | InputOption::VALUE_REQUIRED, 36 | 'The page uid for which the TSconfig is parsed. If not set then first found root page will be used.' 37 | ); 38 | } 39 | 40 | /** 41 | * @param InputInterface $input 42 | * @param OutputInterface $output 43 | * @throws \InvalidArgumentException 44 | * @return int 45 | * @throws \Exception 46 | */ 47 | protected function execute(InputInterface $input, OutputInterface $output) 48 | { 49 | $io = new SymfonyStyle($input, $output); 50 | $io->title($this->getDescription()); 51 | $rootPageForTsConfig = $input->hasOption('rootPageForTsConfig') && $input->getOption('rootPageForTsConfig') !== null ? $input->getOption('rootPageForTsConfig') : null; 52 | 53 | $objectManager = GeneralUtility::makeInstance(ObjectManager::class); 54 | 55 | $configurator = GeneralUtility::makeInstance(Configurator::class); 56 | $configurator->setConfigByPage($rootPageForTsConfig); 57 | $configurator->init(); 58 | 59 | /** @var OptimizeImagesFalService $optimizeImagesFalService */ 60 | $optimizeImagesFalService = $objectManager->get(OptimizeImagesFalService::class, $configurator->getConfig()); 61 | $optimizeImagesFalService->resetOptimizationFlag(); 62 | 63 | $io->writeln('Done succesfully.'); 64 | return 0; 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /Classes/Command/ResetOptimizationFlagForFolders.php: -------------------------------------------------------------------------------- 1 | setDescription('Reset optimized flag for folders images so all files can be optimized once more.') 32 | ->addOption( 33 | 'rootPageForTsConfig', 34 | null, 35 | InputOption::VALUE_REQUIRED, 36 | 'The page uid for which the TSconfig is parsed. If not set then first found root page will be used.' 37 | ); 38 | } 39 | 40 | /** 41 | * @param InputInterface $input 42 | * @param OutputInterface $output 43 | * @return int 44 | * @throws \InvalidArgumentException 45 | * @throws \Exception 46 | */ 47 | protected function execute(InputInterface $input, OutputInterface $output) 48 | { 49 | $io = new SymfonyStyle($input, $output); 50 | $io->title($this->getDescription()); 51 | $rootPageForTsConfig = $input->hasOption('rootPageForTsConfig') && $input->getOption('rootPageForTsConfig') !== null ? $input->getOption('rootPageForTsConfig') : null; 52 | $objectManager = GeneralUtility::makeInstance(ObjectManager::class); 53 | 54 | $configurator = GeneralUtility::makeInstance(Configurator::class); 55 | $configurator->setConfigByPage($rootPageForTsConfig); 56 | $configurator->init(); 57 | 58 | /** @var OptimizeImagesFolderService $optimizeImagesFolderService */ 59 | $optimizeImagesFolderService = $objectManager->get(OptimizeImagesFolderService::class, $configurator->getConfig()); 60 | $optimizeImagesFolderService->resetOptimizationFlag(); 61 | 62 | $io->writeln('Done succesfully.'); 63 | return 0; 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /Classes/Configuration/Configurator.php: -------------------------------------------------------------------------------- 1 | config = $config; 54 | } 55 | 56 | /** 57 | * Set configuration via direct array 58 | * 59 | * @param array $config 60 | */ 61 | public function setConfig(array $config) 62 | { 63 | $this->config = $config; 64 | } 65 | 66 | /** 67 | * Set configuration via page Id 68 | * 69 | * @param int|null $pageId 70 | * @throws Exception 71 | */ 72 | public function setConfigByPage($pageId) 73 | { 74 | $this->config = $this->getConfigForPage($pageId); 75 | } 76 | 77 | /** 78 | * @return array|null 79 | */ 80 | public function getConfig() 81 | { 82 | return $this->config; 83 | } 84 | 85 | /** 86 | * Returns providers with given type 87 | * 88 | * @param string $providerType 89 | * @param string $fileType 90 | * @return array 91 | */ 92 | public function getProviders($providerType, $fileType) 93 | { 94 | $providers = !empty($this->providers[$providerType]) 95 | ? $this->providers[$providerType] 96 | : []; 97 | 98 | return array_filter($providers, function ($provider) use ($fileType) { 99 | $providerFileTypes = explode(',', $provider['fileType']); 100 | return in_array($fileType, $providerFileTypes); 101 | }); 102 | } 103 | 104 | /** 105 | * Initialize configurator 106 | * 107 | * @throws Exception 108 | */ 109 | public function init() 110 | { 111 | if ($this->config === null) { 112 | throw new Exception('Configuration not set for ImageOpt ext'); 113 | } 114 | 115 | if (!$this->isConfigBranchValid('providers')) { 116 | throw new Exception('Providers are not defined.'); 117 | } 118 | 119 | if (!$this->isConfigBranchValid('mode')) { 120 | throw new Exception('Optimize modes are not defined.'); 121 | } 122 | 123 | foreach ($this->config['mode'] as $name => &$optimizeMode) { 124 | if (empty($optimizeMode['name'])) { 125 | $optimizeMode['name'] = $name; 126 | } 127 | } 128 | foreach ($this->config['providers'] as $providerKey => $providerValues) { 129 | if ($this->isConfigBranchValid('providersDefault')) { 130 | $this->config['providers'][$providerKey] = ArrayUtility::arrayMergeAsFallback( 131 | $providerValues, 132 | $this->config['providersDefault'] 133 | ); 134 | } 135 | if (!is_array($providerValues['executors'])) { 136 | throw new Exception('No executors defined for provider: "' . $providerKey . '""'); 137 | } 138 | foreach ($providerValues['executors'] as $executorKey => $executorValues) { 139 | if ($this->isConfigBranchValid('executorsDefault')) { 140 | $this->config['providers'][$providerKey]['executors'][$executorKey] = ArrayUtility::arrayMergeAsFallback( 141 | $executorValues, 142 | $this->config['executorsDefault'] 143 | ); 144 | } 145 | } 146 | } 147 | 148 | foreach ($this->config['providers'] as $providerKey => $providerValues) { 149 | foreach (GeneralUtility::trimExplode(',', $providerValues['type']) as $type) { 150 | $providerTyped = $providerValues; 151 | $providerTyped['type'] = $type; 152 | if (isset($providerTyped['typeOverride'][$type])) { 153 | \TYPO3\CMS\Core\Utility\ArrayUtility::mergeRecursiveWithOverrule( 154 | $providerTyped, 155 | $providerValues['typeOverride'][$type] 156 | ); 157 | } 158 | unset($providerTyped['typeOverride']); 159 | if (!isset($this->providers[$type])) { 160 | $this->providers[$type] = []; 161 | } 162 | $this->providers[$type][$providerKey] = $providerTyped; 163 | } 164 | } 165 | 166 | foreach ($this->config['providers'] as $providerKey => $providerValues) { 167 | if (empty($providerValues['type'])) { 168 | throw new Exception('Provider types is not set for provider: "' . $providerKey . '"'); 169 | } 170 | if (empty($providerValues['fileType'])) { 171 | throw new Exception('File types is not set for provider: "' . $providerKey . '"'); 172 | } 173 | } 174 | } 175 | 176 | /** 177 | * Return option from configuration array with support for nested comma separated notation as "option1.suboption" 178 | * 179 | * @param string $name Configuration 180 | * @param array $overwriteConfig 181 | * @return array|null|string 182 | */ 183 | public function getOption($name = null, $overwriteConfig = null) 184 | { 185 | $config = null; 186 | if (is_string($name)) { 187 | $pieces = explode('.', $name); 188 | if ($pieces !== false) { 189 | if ($overwriteConfig === null) { 190 | $config = $this->config; 191 | } else { 192 | $config = $overwriteConfig; 193 | } 194 | foreach ($pieces as $piece) { 195 | if (!is_array($config) || !array_key_exists($piece, $config)) { 196 | return null; 197 | } 198 | $config = $config[$piece]; 199 | } 200 | } 201 | } 202 | 203 | return $config; 204 | } 205 | 206 | /** 207 | * Return config for given page. 208 | * 209 | * @param int|null $rootPageForTsConfig 210 | * @return array 211 | * @throws Exception 212 | */ 213 | public function getConfigForPage($rootPageForTsConfig = null) 214 | { 215 | if ($rootPageForTsConfig === null) { 216 | $rootPageForTsConfigRow = GeneralUtility::makeInstance(\SourceBroker\Imageopt\Resource\PageRepository::class) 217 | ->getRootPages(); 218 | if ($rootPageForTsConfigRow !== null) { 219 | $rootPageForTsConfig = $rootPageForTsConfigRow['uid']; 220 | } else { 221 | throw new Exception('Can not detect the root page to generate page TSconfig.', 1501700792654); 222 | } 223 | } 224 | $serviceConfig = GeneralUtility::makeInstance(TypoScriptService::class) 225 | ->convertTypoScriptArrayToPlainArray(BackendUtility::getPagesTSconfig($rootPageForTsConfig)); 226 | if (isset($serviceConfig['tx_imageopt'])) { 227 | return $serviceConfig['tx_imageopt']; 228 | } else { 229 | throw new Exception('There is no TSconfig for tx_imageopt in the root page id=' . $rootPageForTsConfig, 1501692752398); 230 | } 231 | } 232 | 233 | /** 234 | * @param string $branch 235 | * @return bool 236 | */ 237 | protected function isConfigBranchValid($branch) 238 | { 239 | return !empty($this->config[$branch]) && is_array($this->config[$branch]); 240 | } 241 | } 242 | -------------------------------------------------------------------------------- /Classes/Domain/Model/AbstractBaseResult.php: -------------------------------------------------------------------------------- 1 | sizeBefore; 40 | } 41 | 42 | /** 43 | * @param string $sizeBefore 44 | * @return static 45 | */ 46 | public function setSizeBefore($sizeBefore) 47 | { 48 | $this->sizeBefore = $sizeBefore; 49 | return $this; 50 | } 51 | 52 | /** 53 | * @return string $sizeAfter 54 | */ 55 | public function getSizeAfter() 56 | { 57 | return $this->sizeAfter; 58 | } 59 | 60 | /** 61 | * @param string $sizeAfter 62 | * @return static 63 | */ 64 | public function setSizeAfter($sizeAfter) 65 | { 66 | $this->sizeAfter = $sizeAfter; 67 | return $this; 68 | } 69 | 70 | /** 71 | * @return string $optimizationBytes 72 | */ 73 | public function getOptimizationBytes() 74 | { 75 | if ($this->optimizationBytes === null) { 76 | $this->optimizationBytes = (int)$this->sizeBefore - (int)$this->sizeAfter; 77 | } 78 | return $this->optimizationBytes; 79 | } 80 | 81 | /** 82 | * @return string $optimizationPercentage 83 | */ 84 | public function getOptimizationPercentage() 85 | { 86 | if (!$this->sizeBefore) { 87 | return 0; 88 | } 89 | 90 | if ($this->optimizationPercent === null) { 91 | $this->optimizationPercent = ((int)$this->sizeBefore - (int)$this->sizeAfter) / (float)$this->sizeBefore * 100; 92 | } 93 | return $this->optimizationPercent; 94 | } 95 | 96 | /** 97 | * @return bool 98 | */ 99 | public function isExecutedSuccessfully() 100 | { 101 | return $this->executedSuccessfully; 102 | } 103 | 104 | /** 105 | * @param bool $executedSuccessfully 106 | * @return static 107 | */ 108 | public function setExecutedSuccessfully($executedSuccessfully) 109 | { 110 | $this->executedSuccessfully = $executedSuccessfully; 111 | return $this; 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /Classes/Domain/Model/ExecutorResult.php: -------------------------------------------------------------------------------- 1 | command; 57 | } 58 | 59 | /** 60 | * Sets the command 61 | * 62 | * @param string $command 63 | * @return void 64 | */ 65 | public function setCommand($command) 66 | { 67 | $this->command = $command; 68 | } 69 | 70 | /** 71 | * Returns the commandOutput 72 | * 73 | * @return string $commandOutput 74 | */ 75 | public function getCommandOutput() 76 | { 77 | return $this->commandOutput; 78 | } 79 | 80 | /** 81 | * Sets the commandOutput 82 | * 83 | * @param string $commandOutput 84 | * @return void 85 | */ 86 | public function setCommandOutput($commandOutput) 87 | { 88 | $this->commandOutput = $commandOutput; 89 | } 90 | 91 | /** 92 | * Returns the commandStatus 93 | * 94 | * @return string $commandStatus 95 | */ 96 | public function getCommandStatus() 97 | { 98 | return $this->commandStatus; 99 | } 100 | 101 | /** 102 | * Sets the commandStatus 103 | * 104 | * @param string $commandStatus 105 | * @return void 106 | */ 107 | public function setCommandStatus($commandStatus) 108 | { 109 | $this->commandStatus = $commandStatus; 110 | } 111 | 112 | /** 113 | * Returns the errorMessage 114 | * 115 | * @return string errorMessage 116 | */ 117 | public function getErrorMessage() 118 | { 119 | return $this->errorMessage; 120 | } 121 | 122 | /** 123 | * Sets the errorMessage 124 | * 125 | * @param string $errorMessage 126 | * @return void 127 | */ 128 | public function setErrorMessage($errorMessage) 129 | { 130 | $this->errorMessage = $errorMessage; 131 | } 132 | } 133 | -------------------------------------------------------------------------------- /Classes/Domain/Model/ModeResult.php: -------------------------------------------------------------------------------- 1 | 41 | */ 42 | protected $stepResults; 43 | 44 | public function __construct() 45 | { 46 | $this->stepResults = new ObjectStorage(); 47 | } 48 | 49 | /** 50 | * @return string $fileAbsolutePath 51 | */ 52 | public function getFileAbsolutePath() 53 | { 54 | return $this->fileAbsolutePath; 55 | } 56 | 57 | /** 58 | * @param string $fileAbsolutePath 59 | * @return static 60 | */ 61 | public function setFileAbsolutePath($fileAbsolutePath) 62 | { 63 | $this->fileAbsolutePath = $fileAbsolutePath; 64 | return $this; 65 | } 66 | 67 | /** 68 | * @return string $fileAbsolutePath 69 | */ 70 | public function getName() 71 | { 72 | return $this->name; 73 | } 74 | 75 | /** 76 | * @param string $name 77 | * @return static 78 | */ 79 | public function setName($name) 80 | { 81 | $this->name = $name; 82 | return $this; 83 | } 84 | 85 | /** 86 | * @return string $fileAbsolutePath 87 | */ 88 | public function getDescription() 89 | { 90 | return $this->description; 91 | } 92 | 93 | /** 94 | * @param string $description 95 | * @return static 96 | */ 97 | public function setDescription($description) 98 | { 99 | $this->description = $description; 100 | return $this; 101 | } 102 | 103 | /** 104 | * @param \SourceBroker\Imageopt\Domain\Model\StepResult $stepResult 105 | * @return static 106 | */ 107 | public function addStepResult(StepResult $stepResult) 108 | { 109 | $this->stepResults->attach($stepResult); 110 | return $this; 111 | } 112 | 113 | /** 114 | * @return \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\SourceBroker\Imageopt\Domain\Model\StepResult> 115 | */ 116 | public function getStepResults() 117 | { 118 | return $this->stepResults; 119 | } 120 | 121 | /** 122 | * Returns number of successfully runned executors 123 | * 124 | * @return int 125 | */ 126 | public function getExecutedSuccessfullyNum() 127 | { 128 | $num = 0; 129 | foreach ($this->stepResults as $result) { 130 | if ($result->isExecutedSuccessfully()) { 131 | ++$num; 132 | } 133 | } 134 | return $num; 135 | } 136 | 137 | /** 138 | * Returns the info 139 | * 140 | * @return string $info 141 | */ 142 | public function getInfo() 143 | { 144 | return $this->info; 145 | } 146 | 147 | /** 148 | * Sets the info 149 | * 150 | * @param string $info 151 | * @return static 152 | */ 153 | public function setInfo($info) 154 | { 155 | $this->info = $info; 156 | return $this; 157 | } 158 | } 159 | -------------------------------------------------------------------------------- /Classes/Domain/Model/ProviderResult.php: -------------------------------------------------------------------------------- 1 | 30 | * @cascade remove 31 | */ 32 | protected $executorsResults = null; 33 | 34 | /** 35 | * __construct 36 | */ 37 | public function __construct() 38 | { 39 | //Do not remove the next line: It would break the functionality 40 | $this->initStorageObjects(); 41 | } 42 | 43 | /** 44 | * Initializes all ObjectStorage properties 45 | * Do not modify this method! 46 | * It will be rewritten on each save in the extension builder 47 | * You may modify the constructor of this class instead 48 | * 49 | * @return void 50 | */ 51 | protected function initStorageObjects() 52 | { 53 | $this->executorsResults = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage(); 54 | } 55 | 56 | /** 57 | * Adds a ExecutorResult 58 | * 59 | * @param \SourceBroker\Imageopt\Domain\Model\ExecutorResult $executorsResult 60 | * @return void 61 | */ 62 | public function addExecutorsResult(\SourceBroker\Imageopt\Domain\Model\ExecutorResult $executorsResult) 63 | { 64 | $this->executorsResults->attach($executorsResult); 65 | } 66 | 67 | /** 68 | * Removes a ExecutorResult 69 | * 70 | * @param \SourceBroker\Imageopt\Domain\Model\ExecutorResult $executorsResultToRemove The ExecutorResult to be removed 71 | * @return void 72 | */ 73 | public function removeExecutorsResult(\SourceBroker\Imageopt\Domain\Model\ExecutorResult $executorsResultToRemove) 74 | { 75 | $this->executorsResults->detach($executorsResultToRemove); 76 | } 77 | 78 | /** 79 | * Returns the executorsResults 80 | * 81 | * @return \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\SourceBroker\Imageopt\Domain\Model\ExecutorResult> $executorsResults 82 | */ 83 | public function getExecutorsResults() 84 | { 85 | return $this->executorsResults; 86 | } 87 | 88 | /** 89 | * Sets the executorsResults 90 | * 91 | * @param \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\SourceBroker\Imageopt\Domain\Model\ExecutorResult> $executorsResults 92 | * @return void 93 | */ 94 | public function setExecutorsResults(\TYPO3\CMS\Extbase\Persistence\ObjectStorage $executorsResults) 95 | { 96 | $this->executorsResults = $executorsResults; 97 | } 98 | 99 | /** 100 | * Returns the name 101 | * 102 | * @return string $name 103 | */ 104 | public function getName() 105 | { 106 | return $this->name; 107 | } 108 | 109 | /** 110 | * Sets the name 111 | * 112 | * @param string $name 113 | * @return void 114 | */ 115 | public function setName($name) 116 | { 117 | $this->name = $name; 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /Classes/Domain/Model/StepResult.php: -------------------------------------------------------------------------------- 1 | 35 | */ 36 | protected $providersResults = null; 37 | 38 | /** 39 | * @return string $fileAbsolutePath 40 | */ 41 | public function getDescription() 42 | { 43 | return $this->description; 44 | } 45 | 46 | /** 47 | * @param string $description 48 | * @return static 49 | */ 50 | public function setDescription($description) 51 | { 52 | $this->description = $description; 53 | return $this; 54 | } 55 | 56 | /** 57 | * @var string 58 | */ 59 | protected $info = ''; 60 | 61 | /** 62 | * __construct 63 | */ 64 | public function __construct() 65 | { 66 | //Do not remove the next line: It would break the functionality 67 | $this->initStorageObjects(); 68 | } 69 | 70 | /** 71 | * Initializes all ObjectStorage properties 72 | * Do not modify this method! 73 | * It will be rewritten on each save in the extension builder 74 | * You may modify the constructor of this class instead 75 | * 76 | * @return void 77 | */ 78 | protected function initStorageObjects() 79 | { 80 | $this->providersResults = new \TYPO3\CMS\Extbase\Persistence\ObjectStorage(); 81 | } 82 | 83 | /** 84 | * Adds a ProviderResult 85 | * 86 | * @param \SourceBroker\Imageopt\Domain\Model\ProviderResult $providersResult 87 | * @return static 88 | */ 89 | public function addProvidersResult(\SourceBroker\Imageopt\Domain\Model\ProviderResult $providersResult) 90 | { 91 | $this->providersResults->attach($providersResult); 92 | return $this; 93 | } 94 | 95 | /** 96 | * Returns the providersResults 97 | * 98 | * @return \TYPO3\CMS\Extbase\Persistence\ObjectStorage<\SourceBroker\Imageopt\Domain\Model\ProviderResult> $providersResults 99 | */ 100 | public function getProvidersResults() 101 | { 102 | return $this->providersResults; 103 | } 104 | 105 | /** 106 | * @return string 107 | */ 108 | public function getName() 109 | { 110 | return $this->name; 111 | } 112 | 113 | /** 114 | * @param string $name 115 | * @return static 116 | */ 117 | public function setName($name) 118 | { 119 | $this->name = $name; 120 | return $this; 121 | } 122 | 123 | /** 124 | * Returns the providerWinnerName 125 | * 126 | * @return string $providerWinnerName 127 | */ 128 | public function getProviderWinnerName() 129 | { 130 | return $this->providerWinnerName; 131 | } 132 | 133 | /** 134 | * Sets the providerWinnerName 135 | * 136 | * @param string $providerWinnerName 137 | * @return static 138 | */ 139 | public function setProviderWinnerName($providerWinnerName) 140 | { 141 | $this->providerWinnerName = $providerWinnerName; 142 | return $this; 143 | } 144 | 145 | /** 146 | * Returns number of successfully runned executors 147 | * 148 | * @return int 149 | */ 150 | public function getExecutedSuccessfullyNum() 151 | { 152 | $num = 0; 153 | foreach ($this->providersResults as $result) { 154 | if ($result->isExecutedSuccessfully()) { 155 | ++$num; 156 | } 157 | } 158 | return $num; 159 | } 160 | 161 | /** 162 | * Returns the info 163 | * 164 | * @return string $info 165 | */ 166 | public function getInfo() 167 | { 168 | return $this->info; 169 | } 170 | 171 | /** 172 | * Sets the info 173 | * 174 | * @param string $info 175 | * @return static 176 | */ 177 | public function setInfo($info) 178 | { 179 | $this->info = $info; 180 | return $this; 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /Classes/Domain/Repository/ExecutorResultRepository.php: -------------------------------------------------------------------------------- 1 | setExecutedSuccessfully(false); 48 | return $executorResult; 49 | } 50 | 51 | /** 52 | * @param Configurator $configurator 53 | * @return string 54 | */ 55 | public function getExecutorQuality(Configurator $configurator) 56 | { 57 | $executorQuality = ''; 58 | if (!empty($configurator->getOption('options.quality.options')) && !empty($configurator->getOption('options.quality.value'))) { 59 | $quality = $configurator->getOption('options.quality.value'); 60 | $options = $configurator->getOption('options.quality.options'); 61 | if (isset($options[$quality])) { 62 | $executorQuality = $options[$quality]; 63 | } 64 | } 65 | return $executorQuality; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /Classes/Executor/OptimizationExecutorInterface.php: -------------------------------------------------------------------------------- 1 | setExecutedSuccessfully(false); 77 | if ($this->initConfiguration($configurator)) { 78 | $executorResult->setSizeBefore(filesize($inputImageAbsolutePath)); 79 | $this->process($inputImageAbsolutePath, $executorResult); 80 | $executorResult->setSizeAfter(filesize($inputImageAbsolutePath)); 81 | } else { 82 | $executorResult->setErrorMessage('Unable to initialize executor - check configuration'); 83 | } 84 | return $executorResult; 85 | } 86 | 87 | /** 88 | * Initialize executor 89 | * 90 | * @param Configurator $configurator 91 | * @return bool 92 | */ 93 | protected function initConfiguration(Configurator $configurator) 94 | { 95 | $timeout = $configurator->getOption('timeout'); 96 | if ($timeout !== null) { 97 | $this->timeout = (int)$timeout; 98 | } 99 | 100 | $proxy = $configurator->getOption('proxy'); 101 | if (is_array($proxy) && !empty($proxy)) { 102 | $this->proxy = $proxy; 103 | } 104 | 105 | $apiUrl = $configurator->getOption('api.url'); 106 | if (is_array($apiUrl) && !empty($apiUrl)) { 107 | $this->url = $apiUrl; 108 | } else { 109 | return false; 110 | } 111 | 112 | $apiAuth = $configurator->getOption('api.auth'); 113 | if (is_array($apiAuth) && !empty($apiAuth)) { 114 | $this->auth = $apiAuth; 115 | } else { 116 | return false; 117 | } 118 | 119 | $options = $configurator->getOption('api.options'); 120 | if (is_array($options) && !empty($options)) { 121 | $this->apiOptions = $options; 122 | } 123 | 124 | $options = $configurator->getOption('options'); 125 | if (is_array($options) && !empty($options)) { 126 | $this->executorOptions = $options; 127 | } 128 | 129 | return true; 130 | } 131 | 132 | /** 133 | * Process specific executor logic 134 | * 135 | * @param string $inputImageAbsolutePath Absolute path/file with original image 136 | * @param ExecutorResult $executorResult 137 | */ 138 | protected function process($inputImageAbsolutePath, ExecutorResult $executorResult) 139 | { 140 | } 141 | 142 | /** 143 | * Executes request to remote server 144 | * 145 | * @param string|array $data Data of request 146 | * @param string $url Url to execute request 147 | * @param array $options Additional options 148 | * @return array 149 | */ 150 | protected function request($data, $url, array $options = []) 151 | { 152 | $curl = curl_init(); 153 | 154 | if (isset($options['curl'])) { 155 | curl_setopt_array($curl, $options['curl']); 156 | } 157 | 158 | if ($this->proxy) { 159 | curl_setopt($curl, CURLOPT_PROXY, $this->proxy['host']); 160 | curl_setopt($curl, CURLOPT_PROXYTYPE, CURLPROXY_HTTP); 161 | 162 | if (isset($this->proxy['port'])) { 163 | curl_setopt($curl, CURLOPT_PROXYPORT, $this->proxy['port']); 164 | } 165 | 166 | $creds = ''; 167 | 168 | if (isset($this->proxy['user'])) { 169 | $creds .= $this->proxy['user']; 170 | } 171 | if (isset($this->proxy['pass'])) { 172 | $creds .= ':' . $this->proxy['pass']; 173 | } 174 | 175 | if ($creds != '') { 176 | curl_setopt($curl, CURLOPT_PROXYAUTH, CURLAUTH_ANY); 177 | curl_setopt($curl, CURLOPT_PROXYUSERPWD, $creds); 178 | } 179 | } 180 | 181 | curl_setopt($curl, CURLOPT_URL, $url); 182 | curl_setopt( 183 | $curl, 184 | CURLOPT_USERAGENT, 185 | 'Mozilla/5.0 (Windows NT 6.1; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/40.0.2214.85 Safari/537.36' 186 | ); 187 | curl_setopt($curl, CURLOPT_BINARYTRANSFER, 1); //kraken? 188 | curl_setopt($curl, CURLOPT_RETURNTRANSFER, 1); 189 | curl_setopt($curl, CURLOPT_POST, 1);//tiny? 190 | curl_setopt($curl, CURLOPT_POSTFIELDS, $data); 191 | curl_setopt($curl, CURLOPT_FAILONERROR, 0); 192 | curl_setopt($curl, CURLOPT_TIMEOUT, $this->timeout); 193 | //curl_setopt($curl, CURLOPT_FOLLOWLOCATION, true);//imageopt? 194 | $response = curl_exec($curl); 195 | 196 | $httpCode = (int)curl_getinfo($curl, CURLINFO_HTTP_CODE); 197 | $headerSize = curl_getinfo($curl, CURLINFO_HEADER_SIZE); 198 | 199 | $result = [ 200 | 'response' => $response, 201 | 'http_code' => $httpCode, 202 | 'header_size' => $headerSize, 203 | 'error' => curl_error($curl), 204 | ]; 205 | curl_close($curl); 206 | 207 | return $result; 208 | } 209 | 210 | /** 211 | * Handles response errors 212 | * 213 | * @param array $response 214 | * @return string|null 215 | */ 216 | protected function handleResponseError(array $response) 217 | { 218 | $result = null; 219 | 220 | if ($response['error']) { 221 | $result = 'cURL Error: ' . $response['error']; 222 | } else { 223 | switch ($response['http_code']) { 224 | case 401: 225 | $result = 'HTTP unauthorized'; 226 | break; 227 | case 403: 228 | $result = 'HTTP forbidden'; 229 | break; 230 | case 429: 231 | $result = 'Limit out'; 232 | break; 233 | default: 234 | if (!in_array($response['http_code'], [200, 201])) { 235 | $result = 'HTTP code: ' . $response['http_code']; 236 | } elseif (empty($response['response'])) { 237 | $result = 'Empty response'; 238 | } 239 | } 240 | } 241 | 242 | return $result; 243 | } 244 | 245 | /** 246 | * Gets the image and saves 247 | * 248 | * @param string $inputImageAbsolutePath Absolute path to target image 249 | * @param string $url Url of the image to download 250 | * @return bool Returns true if the image exists and will be saved 251 | */ 252 | protected function getFileFromRemoteServer($inputImageAbsolutePath, $url) 253 | { 254 | $headers = get_headers($url); 255 | 256 | if (stripos($headers[0], '200 OK')) { 257 | file_put_contents($inputImageAbsolutePath, fopen($url, 'r')); 258 | return true; 259 | } 260 | 261 | return false; 262 | } 263 | } 264 | -------------------------------------------------------------------------------- /Classes/Executor/OptimizationExecutorRemoteImageoptim.php: -------------------------------------------------------------------------------- 1 | auth['key']) || !isset($this->url['upload'])) { 45 | return false; 46 | } 47 | if (!isset($this->apiOptions['quality']) && isset($this->executorOptions['quality'])) { 48 | $this->apiOptions['quality'] = $this->getExecutorQuality($configurator); 49 | } 50 | return true; 51 | } 52 | 53 | /** 54 | * Upload file to imageoptim.com and save it if optimization will be success 55 | * 56 | * @param string $inputImageAbsolutePath Absolute path/file with original image 57 | * @param ExecutorResult $executorResult 58 | */ 59 | protected function process($inputImageAbsolutePath, ExecutorResult $executorResult) 60 | { 61 | $optionsString = []; 62 | foreach ($this->apiOptions as $name => $value) { 63 | if (is_numeric($name)) { 64 | $optionsString[] = $value; 65 | } else { 66 | $optionsString[] = $name . '=' . $value; 67 | } 68 | } 69 | $url = implode('/', [ 70 | $this->url['upload'], 71 | $this->auth['key'], 72 | implode(',', $optionsString) 73 | ]); 74 | $executorResult->setCommand('URL: ' . $url . " \n"); 75 | $result = $this->request(['file' => curl_file_create($inputImageAbsolutePath)], $url); 76 | if ($result['success']) { 77 | if (isset($result['response'])) { 78 | if ((bool)file_put_contents($inputImageAbsolutePath, $result['response'])) { 79 | $executorResult->setExecutedSuccessfully(true); 80 | $executorResult->setCommandStatus('Done'); 81 | } else { 82 | $executorResult->setErrorMessage('Unable to save image'); 83 | $executorResult->setCommandStatus('Failed'); 84 | } 85 | } else { 86 | $message = $result['error'] ?? 'Undefined error'; 87 | $executorResult->setErrorMessage($message); 88 | $executorResult->setCommandStatus('Failed'); 89 | } 90 | } else { 91 | $executorResult->setErrorMessage($result['error']); 92 | $executorResult->setCommandStatus('Failed'); 93 | } 94 | } 95 | 96 | /** 97 | * Executes request to remote server 98 | * 99 | * @param array $data Array with data of file 100 | * @param string $url Url to execute request 101 | * @param array $params Additional parameters 102 | * @return array 103 | */ 104 | protected function request($data, $url, array $params = []) 105 | { 106 | $responseFromAPI = parent::request($data, $url, [ 107 | 'curl' => [], 108 | ]); 109 | $handledResponse = $this->handleResponseError($responseFromAPI); 110 | $result = null; 111 | if ($handledResponse !== null) { 112 | $result = [ 113 | 'success' => false, 114 | 'error' => $handledResponse 115 | ]; 116 | } else { 117 | $result = [ 118 | 'success' => true, 119 | 'response' => $responseFromAPI['response'], 120 | ]; 121 | } 122 | return $result; 123 | } 124 | } 125 | -------------------------------------------------------------------------------- /Classes/Executor/OptimizationExecutorRemoteKraken.php: -------------------------------------------------------------------------------- 1 | auth['key']) || !isset($this->auth['pass'])) { 45 | $result = false; 46 | } elseif (!isset($this->url['upload'])) { 47 | $result = false; 48 | } 49 | if (!isset($this->apiOptions['quality']) && isset($this->executorOptions['quality'])) { 50 | $this->apiOptions['quality'] = (int)$this->executorOptions['quality']['value']; 51 | } 52 | if (isset($this->apiOptions['quality'])) { 53 | $this->apiOptions['quality'] = (int)$this->apiOptions['quality']; 54 | } 55 | } 56 | return $result; 57 | } 58 | 59 | /** 60 | * Upload file to kraken.io and save it if optimization will be success 61 | * 62 | * @param string $inputImageAbsolutePath Absolute path/file with original image 63 | * @param ExecutorResult $executorResult 64 | */ 65 | protected function process($inputImageAbsolutePath, ExecutorResult $executorResult) 66 | { 67 | $options = $this->apiOptions; 68 | $options['wait'] = true; // wait for processed file (forced option) 69 | $options['auth'] = [ 70 | 'api_key' => $this->auth['key'], 71 | 'api_secret' => $this->auth['pass'], 72 | ]; 73 | foreach ($options as $key => $value) { 74 | if ($value === 'true' || $value === 'false') { 75 | $options[$key] = $value === 'true'; 76 | } 77 | } 78 | $post = [ 79 | 'file' => curl_file_create($inputImageAbsolutePath), 80 | 'data' => json_encode($options), 81 | ]; 82 | $result = $this->request($post, $this->url['upload'], ['type' => 'upload']); 83 | $executorResult->setCommand('URL: ' . $this->url['upload'] . " \n" . 'POST: ' . $post['data']); 84 | if ($result['success']) { 85 | if (isset($result['response']['kraked_url'])) { 86 | $download = $this->getFileFromRemoteServer($inputImageAbsolutePath, $result['response']['kraked_url']); 87 | if ($download) { 88 | $executorResult->setExecutedSuccessfully(true); 89 | $executorResult->setCommandStatus('Done'); 90 | } else { 91 | $executorResult->setErrorMessage('Unable to download image'); 92 | $executorResult->setCommandStatus('Failed'); 93 | } 94 | } else { 95 | $executorResult->setErrorMessage('Download URL not defined'); 96 | $executorResult->setCommandStatus('Failed'); 97 | } 98 | } else { 99 | $executorResult->setErrorMessage($result['error']); 100 | $executorResult->setCommandStatus('Failed'); 101 | } 102 | } 103 | 104 | /** 105 | * Executes request to remote server 106 | * 107 | * @param array $data Array with data and file path 108 | * @param string $url API kraken.io url 109 | * @param array $params Additional parameters 110 | * @return array Result of optimization includes the response from the kraken.io 111 | */ 112 | protected function request($data, $url, array $params = []) 113 | { 114 | $options = [ 115 | 'curl' => [ 116 | CURLOPT_CAINFO => ExtensionManagementUtility::extPath('imageopt') . 'Resources/Private/Cert/cacert.pem', 117 | CURLOPT_SSL_VERIFYPEER => 1, 118 | ], 119 | ]; 120 | if (isset($params['type']) && $params['type'] === 'url') { 121 | $options['curl'][CURLOPT_HTTPHEADER] = [ 122 | 'Content-Type: application/json', 123 | ]; 124 | } 125 | $responseFromAPI = parent::request($data, $url, $options); 126 | $handledResponse = $this->handleResponseError($responseFromAPI); 127 | if ($handledResponse !== null) { 128 | return [ 129 | 'success' => false, 130 | 'error' => $handledResponse 131 | ]; 132 | } 133 | $response = json_decode($responseFromAPI['response'], true, 512); 134 | if ($response === null) { 135 | $result = [ 136 | 'success' => false, 137 | 'error' => 'Unable to decode JSON', 138 | ]; 139 | } elseif (!isset($response['success']) || $response['success'] === false) { 140 | $message = isset($response['message']) 141 | ? $response['message'] 142 | : 'Undefined error'; 143 | 144 | $result = [ 145 | 'success' => false, 146 | 'error' => 'API error: ' . $message, 147 | ]; 148 | } else { 149 | $result = [ 150 | 'success' => true, 151 | 'response' => $response, 152 | ]; 153 | } 154 | return $result; 155 | } 156 | } 157 | -------------------------------------------------------------------------------- /Classes/Executor/OptimizationExecutorRemoteTinypng.php: -------------------------------------------------------------------------------- 1 | auth['key'])) { 44 | $result = false; 45 | } elseif (!isset($this->url['upload'])) { 46 | $result = false; 47 | } 48 | } 49 | return $result; 50 | } 51 | 52 | /** 53 | * Upload file to tinypng.com and save it if optimization will be success 54 | * 55 | * @param string $inputImageAbsolutePath Absolute path/file with original image 56 | * @param ExecutorResult $executorResult 57 | */ 58 | protected function process($inputImageAbsolutePath, ExecutorResult $executorResult) 59 | { 60 | $executorResult->setCommand('URL: ' . $this->url['upload']); 61 | $result = $this->request(file_get_contents($inputImageAbsolutePath), $this->url['upload']); 62 | if ($result['success']) { 63 | if (isset($result['response']['output']['url'])) { 64 | $download = $this->getFileFromRemoteServer( 65 | $inputImageAbsolutePath, 66 | $result['response']['output']['url'] 67 | ); 68 | 69 | if ($download) { 70 | $executorResult->setExecutedSuccessfully(true); 71 | $executorResult->setCommandStatus('Done'); 72 | } else { 73 | $executorResult->setErrorMessage('Unable to download image'); 74 | $executorResult->setCommandStatus('Failed'); 75 | } 76 | } else { 77 | $executorResult->setErrorMessage('Download URL not defined'); 78 | $executorResult->setCommandStatus('Failed'); 79 | } 80 | } else { 81 | $executorResult->setErrorMessage($result['error']); 82 | $executorResult->setCommandStatus('Failed'); 83 | } 84 | } 85 | 86 | /** 87 | * Request to tinypng.com using CURL 88 | * 89 | * @param string $data String from image file 90 | * @param string $url API tinypng.com url 91 | * @param array $params Additional parameters 92 | * @return array Result of optimization includes the response from the tinypng.com 93 | */ 94 | protected function request($data, $url, array $params = []) 95 | { 96 | $options = array_merge([ 97 | 'curl' => [ 98 | CURLOPT_HEADER => true, 99 | CURLOPT_USERPWD => 'api:' . $this->auth['key'], 100 | ], 101 | ], $params); 102 | 103 | $responseFromAPI = parent::request($data, $url, $options); 104 | 105 | $handledResponse = $this->handleResponseError($responseFromAPI); 106 | if ($handledResponse !== null) { 107 | return [ 108 | 'success' => false, 109 | 'error' => $handledResponse 110 | ]; 111 | } 112 | 113 | $body = substr($responseFromAPI['response'], $responseFromAPI['header_size']); 114 | return [ 115 | 'success' => true, 116 | 'response' => json_decode($body, true), 117 | ]; 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /Classes/Executor/OptimizationExecutorShell.php: -------------------------------------------------------------------------------- 1 | setExecutedSuccessfully(false); 49 | if (!empty($configurator->getOption('command.exec'))) { 50 | if (is_readable($inputImageAbsolutePath)) { 51 | $execDeclared = (string)$configurator->getOption('command.exec'); 52 | if (pathinfo($execDeclared, PATHINFO_DIRNAME) !== '.') { 53 | $execDetected = $execDeclared; 54 | } else { 55 | $execDetected = CommandUtility::getCommand($execDeclared); 56 | } 57 | if ($execDetected !== false) { 58 | $executorResult->setSizeBefore(filesize($inputImageAbsolutePath)); 59 | $shellCommand = str_replace( 60 | ['{executable}', '{tempFile}', '{quality}'], 61 | [ 62 | $execDetected, 63 | escapeshellarg($inputImageAbsolutePath), 64 | $this->getExecutorQuality($configurator) 65 | ], 66 | $configurator->getOption('command.mask') 67 | ); 68 | $successfulStatuses = [0]; 69 | if (!empty($configurator->getOption('command.successfulExitStatus'))) { 70 | $successfulStatuses = array_merge( 71 | $successfulStatuses, 72 | explode(',', (string)$configurator->getOption('command.successfulExitStatus')) 73 | ); 74 | } 75 | exec($shellCommand, $output, $commandStatus); 76 | clearstatcache(true, $inputImageAbsolutePath); 77 | $executorResult->setSizeAfter(filesize($inputImageAbsolutePath)); 78 | $executorResult->setCommand($shellCommand); 79 | $executorResult->setCommandStatus($commandStatus); 80 | $executorResult->setCommandOutput($output); 81 | $executorResult->setExecutedSuccessfully( 82 | in_array($commandStatus, $successfulStatuses) ? true : false 83 | ); 84 | } else { 85 | $executorResult->setErrorMessage($execDeclared . ' can\'t be found.'); 86 | } 87 | } else { 88 | $executorResult->setErrorMessage('Can not read file to optimize:' . $inputImageAbsolutePath); 89 | } 90 | } else { 91 | $executorResult->setErrorMessage('Variable "command" can not be found in executor configuration.'); 92 | } 93 | return $executorResult; 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /Classes/Provider/OptimizationProvider.php: -------------------------------------------------------------------------------- 1 | executors; 42 | } 43 | 44 | /** 45 | * @param mixed $executors 46 | */ 47 | public function setExecutors($executors) 48 | { 49 | $this->executors = $executors; 50 | } 51 | 52 | /** 53 | * @param $image 54 | * @param Configurator $providerConfigurator 55 | * @return object|ProviderResult 56 | * @throws \Exception 57 | */ 58 | public function optimize($image, Configurator $providerConfigurator) 59 | { 60 | $executorsDone = $executorsSuccessful = 0; 61 | $providerResult = GeneralUtility::makeInstance(ProviderResult::class); 62 | $providerResult->setSizeBefore(filesize($image)); 63 | foreach ((array)$providerConfigurator->getOption('executors') as $executorKey => $executor) { 64 | $executorsDone++; 65 | if (isset($executor['class']) && class_exists($executor['class'])) { 66 | $imageOptimizationProvider = GeneralUtility::makeInstance($executor['class']); 67 | /** @var $executorResult ExecutorResult */ 68 | $executorResult = $imageOptimizationProvider->optimize( 69 | $image, 70 | GeneralUtility::makeInstance(Configurator::class, $executor) 71 | ); 72 | $providerResult->addExecutorsResult($executorResult); 73 | if ($executorResult->isExecutedSuccessfully()) { 74 | $executorsSuccessful++; 75 | } else { 76 | $providerResult->setExecutedSuccessfully(false); 77 | break; 78 | } 79 | } else { 80 | throw new \Exception('No class found: ' . $executor['class'], 1500994839981); 81 | } 82 | } 83 | 84 | $providerResult->setName($providerConfigurator->getOption('providerKey')); 85 | if ($executorsSuccessful == $executorsDone) { 86 | $providerResult->setExecutedSuccessfully(true); 87 | } 88 | clearstatcache(true, $image); 89 | $providerResult->setSizeAfter(filesize($image)); 90 | 91 | return $providerResult; 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /Classes/Resource/PageRepository.php: -------------------------------------------------------------------------------- 1 | getQueryBuilderForTable('pages'); 13 | return $queryBuilder 14 | ->select('uid') 15 | ->from('pages') 16 | ->where( 17 | $queryBuilder->expr()->eq('pid', 0), 18 | $queryBuilder->expr()->eq('deleted', 0) 19 | )->execute()->fetch(); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Classes/Resource/ProcessedFileRepository.php: -------------------------------------------------------------------------------- 1 | getConnectionForTable('sys_file_processedfile') 20 | ->update( 21 | 'sys_file_processedfile', 22 | ['tx_imageopt_executed_successfully' => 0], 23 | ['tx_imageopt_executed_successfully' => 1] 24 | ); 25 | } 26 | 27 | /** 28 | * Get all not optimized images with $limit 29 | */ 30 | public function findNotOptimizedRaw(int $limit, array $extensions) 31 | { 32 | $queryBuilder = GeneralUtility::makeInstance(ConnectionPool::class) 33 | ->getQueryBuilderForTable('sys_file_processedfile'); 34 | 35 | $extensionsQuery = array_map(function ($extension) use ($queryBuilder) { 36 | return $queryBuilder->expr()->like('identifier', $queryBuilder->createNamedParameter('%.' . $extension)); 37 | }, $extensions); 38 | 39 | return $queryBuilder 40 | ->select('*') 41 | ->from('sys_file_processedfile') 42 | ->where( 43 | $queryBuilder->expr()->isNotNull('name'), 44 | $queryBuilder->expr()->eq( 45 | 'tx_imageopt_executed_successfully', 46 | $queryBuilder->createNamedParameter(0, \PDO::PARAM_INT) 47 | ), 48 | $queryBuilder->expr()->neq('task_type', $queryBuilder->createNamedParameter('Image.Preview')), 49 | $queryBuilder->expr()->neq('identifier', $queryBuilder->createNamedParameter('')) 50 | )->andWhere( 51 | $queryBuilder->expr()->orX( 52 | ...$extensionsQuery 53 | ) 54 | )->setMaxResults((int)$limit)->execute()->fetchAll(); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Classes/Service/OptimizeImagesFalService.php: -------------------------------------------------------------------------------- 1 | objectManager = GeneralUtility::makeInstance(ObjectManager::class); 81 | $this->configurator = GeneralUtility::makeInstance(Configurator::class, $config); 82 | $this->configurator->init(); 83 | $this->falProcessedFileRepository = $this->objectManager->get(ProcessedFileRepository::class); 84 | $this->optimizeImageService = $this->objectManager->get(OptimizeImageService::class, $config); 85 | $this->modeResultRepository = $this->objectManager->get(ModeResultRepository::class); 86 | } 87 | 88 | /** 89 | * @param $notOptimizedFileRaw array $notOptimizedProcessedFileRaw, 90 | * @return ModeResult|null 91 | * @throws \Exception 92 | */ 93 | public function optimizeFalProcessedFile($notOptimizedFileRaw) 94 | { 95 | $fileDoesNotExistOrNotReadable = false; 96 | $modeResultInfo = ''; 97 | $modeResults = []; 98 | 99 | /** @var ProcessedFile $processedFal */ 100 | $processedFal = $this->falProcessedFileRepository->findByIdentifier($notOptimizedFileRaw['uid']); 101 | $sourceFile = $processedFal->getForLocalProcessing(false); 102 | 103 | if (file_exists($sourceFile)) { 104 | if (is_readable($sourceFile)) { 105 | $modeResults = $this->optimizeImageService->optimize($sourceFile); 106 | $defaultOptimizationResult = $modeResults['default'] ?? reset($modeResults); 107 | if ($this->configurator->getOption('log.enable')) { 108 | foreach ($modeResults as $modeResult) { 109 | $this->modeResultRepository->add($modeResult); 110 | } 111 | } 112 | if ($defaultOptimizationResult->isExecutedSuccessfully()) { 113 | if ((int)$defaultOptimizationResult->getSizeBefore() > (int)$defaultOptimizationResult->getSizeAfter()) { 114 | $processedFal->updateWithLocalFile( 115 | $this->objectManager->get(TemporaryFileUtility::class)->createTemporaryCopy($sourceFile) 116 | ); 117 | } 118 | $processedFal->updateProperties(['tx_imageopt_executed_successfully' => 1]); 119 | $this->falProcessedFileRepository->update($processedFal); 120 | } 121 | } else { 122 | $fileDoesNotExistOrNotReadable = true; 123 | $modeResultInfo = 'The file above exists but is not readable for imageopt process.'; 124 | } 125 | } else { 126 | $fileDoesNotExistOrNotReadable = true; 127 | $modeResultInfo = 'The file does not exists but exists as reference in "sys_file_processedfile" ' . 128 | 'database table. Seems like it was processed in past but the processed file does not exist now. ' . 129 | 'The record has been deleted from "sys_file_processedfile" table.'; 130 | $processedFal->delete(); 131 | } 132 | 133 | if ($fileDoesNotExistOrNotReadable) { 134 | $modeResult = $this->objectManager->get(ModeResult::class) 135 | ->setFileAbsolutePath(substr($sourceFile, strlen(Environment::getPublicPath() . '/'))) 136 | ->setExecutedSuccessfully(false) 137 | ->setInfo($modeResultInfo); 138 | 139 | if ($this->configurator->getOption('log.enable')) { 140 | $this->objectManager->get(ModeResultRepository::class) 141 | ->add($modeResult); 142 | } 143 | $modeResults[] = $modeResult; 144 | } 145 | 146 | $this->objectManager->get(PersistenceManager::class)->persistAll(); 147 | 148 | return $modeResults; 149 | } 150 | 151 | 152 | public function getFalProcessedFilesToOptimize(int $numberOfImagesToProcess, array $extensions): array 153 | { 154 | return $this->falProcessedFileRepository->findNotOptimizedRaw($numberOfImagesToProcess, $extensions); 155 | } 156 | 157 | public function resetOptimizationFlag() 158 | { 159 | $this->falProcessedFileRepository->resetOptimizationFlag(); 160 | } 161 | } 162 | -------------------------------------------------------------------------------- /Classes/Service/OptimizeImagesFolderService.php: -------------------------------------------------------------------------------- 1 | configurator = $objectManager->get(Configurator::class, $config); 55 | $this->optimizeImageService = $objectManager->get(OptimizeImageService::class, $config); 56 | } 57 | 58 | /** 59 | * @param $numberOfFiles 60 | * @return array 61 | */ 62 | public function getFilesToOptimize($numberOfFiles = 20) 63 | { 64 | $filesToOptimize = []; 65 | $directories = explode(',', preg_replace('/\s+/', '', $this->configurator->getOption('directories'))); 66 | foreach ($directories as $directoryWithExtensions) { 67 | if ($directoryWithExtensions != '') { 68 | if (strpos($directoryWithExtensions, '*') !== false) { 69 | list($directory, $stringExtensions) = explode('*', $directoryWithExtensions); 70 | if (is_dir(Environment::getPublicPath() . '/' . $directory)) { 71 | $directoryIterator = new \RecursiveDirectoryIterator(Environment::getPublicPath() . '/' . $directory); 72 | $iterator = new \RecursiveIteratorIterator($directoryIterator); 73 | $regexIterator = new \RegexIterator( 74 | $iterator, 75 | '/\.(' . strtolower($stringExtensions) . '|' . strtoupper($stringExtensions) . ')$/' 76 | ); 77 | foreach ($regexIterator as $file) { 78 | $perms = fileperms($file->getPathname()); 79 | // Get only 6xx becase 7xx are arleady optimized. 80 | if (!(($perms & 0x0040) ? (($perms & 0x0800) ? false : true) : false)) { 81 | $filesToOptimize[] = $file->getPathname(); 82 | } 83 | if (count($filesToOptimize) > $numberOfFiles) { 84 | break 2; 85 | } 86 | } 87 | } 88 | } 89 | } 90 | } 91 | return $filesToOptimize; 92 | } 93 | 94 | /** 95 | * @param $absoluteFilePath 96 | * @return ModeResult 97 | * @throws \Exception 98 | */ 99 | public function optimizeFolderFile($absoluteFilePath) 100 | { 101 | $modeResults = $this->optimizeImageService->optimize($absoluteFilePath); 102 | 103 | $defaultOptimizationResult = isset($modeResults['default']) 104 | ? $modeResults['default'] 105 | : reset($modeResults); 106 | 107 | if ($defaultOptimizationResult->isExecutedSuccessfully()) { 108 | // Temporary resized images are created by default with permission 644. 109 | // We set the "execute" bit of permission for optimized images (to have 744). 110 | // This way we know what files are still there to be optimized or already optimized. 111 | // If you have better idea how to do it then create issue on github. 112 | exec('chmod u+x ' . escapeshellarg($absoluteFilePath), $out, $status); 113 | if ($status !== 0) { 114 | $defaultOptimizationResult->setInfo('Error executing chmod u+x. Error code: ' 115 | . $status . ' Error message: ' . $out); 116 | } 117 | } 118 | 119 | return $modeResults; 120 | } 121 | 122 | /** 123 | * Reset optimization "done" flag for files in folder 124 | */ 125 | public function resetOptimizationFlag() 126 | { 127 | $directories = explode(',', preg_replace('/\s+/', '', $this->configurator->getOption('directories'))); 128 | foreach ($directories as $directoryWithExtensions) { 129 | if (strpos($directoryWithExtensions, '*') !== false) { 130 | $directory = trim(explode('*', $directoryWithExtensions)[0], '/\\'); 131 | if (is_dir(Environment::getPublicPath() . '/' . $directory)) { 132 | exec('find ' . Environment::getPublicPath() . '/' . $directory . ' -type f -exec chmod u-x {} \;'); 133 | } 134 | } 135 | } 136 | } 137 | } 138 | -------------------------------------------------------------------------------- /Classes/Utility/ArrayUtility.php: -------------------------------------------------------------------------------- 1 | &$value) { 18 | if (is_array($value) && isset($merged[$key]) && is_array($merged[$key])) { 19 | $merged[$key] = self::arrayMergeAsFallback($merged[$key], $value); 20 | } else { 21 | if (!isset($merged[$key])) { 22 | $merged[$key] = $value; 23 | } 24 | } 25 | } 26 | return $merged; 27 | } 28 | 29 | /** 30 | * Converts plain array into nested one 31 | * 32 | * @param array $plainArray 33 | * @return array 34 | */ 35 | public static function plainToNested(array $plainArray) 36 | { 37 | $root = []; 38 | $node =& $root; 39 | while ($part = array_shift($plainArray)) { 40 | if (empty($plainArray)) { 41 | $node = $part; 42 | } else { 43 | $node[$part] = []; 44 | $node =& $node[$part]; 45 | } 46 | } 47 | return $root; 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /Classes/Utility/CliDisplayUtility.php: -------------------------------------------------------------------------------- 1 | getStepResults()->toArray(); 25 | 26 | foreach ($stepResults as $stepKey => $stepResult) { 27 | $providers = []; 28 | $providersScore = []; 29 | 30 | /** @var ProviderResult[] $providerResults */ 31 | $providerResults = $stepResult->getProvidersResults(); 32 | foreach ($providerResults as $providerResult) { 33 | if ($providerResult->isExecutedSuccessfully()) { 34 | $providers[] = $providerResult->getName() . ': ' . round($providerResult->getOptimizationPercentage(), 2) . '%'; 35 | $providersScore[] = $providerResult->getOptimizationPercentage(); 36 | } else { 37 | /** @var ExecutorResult $executorResult */ 38 | $error = []; 39 | foreach ($providerResult->getExecutorsResults()->toArray() as $executorResult) { 40 | if (!$executorResult->isExecutedSuccessfully()) { 41 | $error[] = $executorResult->getErrorMessage(); 42 | } 43 | } 44 | 45 | $limit = 49 - strlen($providerResult->getName()); 46 | $errors = implode(', ', $error); 47 | if (strlen($errors) > $limit) { 48 | $errors = substr($errors, 0, $limit - 2) . '..'; 49 | } 50 | 51 | $providers[] = $providerResult->getName() . ' - failed: ' . $errors; 52 | $providersScore[] = null; 53 | } 54 | } 55 | 56 | uksort($providers, function ($a, $b) use ($providersScore) { 57 | if ($a === null) { 58 | return 1; 59 | } elseif ($b === null) { 60 | return -1; 61 | } 62 | return $providersScore[$a] > $providersScore[$b] ? -1 : 1; 63 | }); 64 | 65 | $providers = array_values($providers); 66 | 67 | foreach ($providers as $i => &$provider) { 68 | $provider = '* ' . $provider; 69 | } 70 | 71 | $providerType = $config['mode'][$modeResult->getName()]['step'][$stepResult->getName()]['providerType']; 72 | if ($stepResult->getProvidersResults()->count() > 0 && 73 | $stepResult->getProvidersResults()->count() == $stepResult->getExecutedSuccessfullyNum()) { 74 | $stepResultFinal = 'All providers executed sucessfuly.'; 75 | } else { 76 | $stepResultFinal = 'Not all providers executed sucessfuly so this step failed.'; 77 | } 78 | 79 | $fileType = strtolower(explode('/', image_type_to_mime_type(getimagesize($modeResult->getFileAbsolutePath())[2]))[1]); 80 | 81 | $statsInfo = 'Step ' . ($stepKey + 1) . "\t\t| Description: " . $stepResult->getDescription() . "\n" 82 | . "\t\t| Providers to find for this step: \"" . $providerType . '" for file type "' . $fileType . "\".\n" 83 | . "\t\t| Found " . $stepResult->getProvidersResults()->count() . ' provider' . ($stepResult->getProvidersResults()->count() > 1 ? 's' : '') 84 | . ' for "' . $providerType . '" and file type "' . $fileType . '". '; 85 | 86 | if (!empty($providers)) { 87 | $statsInfo .= "Running found providers:\n" 88 | . "\t\t| " . implode("\n\t\t| ", $providers); 89 | } 90 | $statsInfo .= "\n\t\t| " . $stepResultFinal . "\n" 91 | . "\t\t| " . $stepResult->getInfo() . "\n"; 92 | if ($stepKey !== $modeResult->getStepResults()->count() - 1) { 93 | $statsInfo .= "\t\t| Passing the output file of this step to Step " . ($stepKey + 2) . '.'; 94 | } 95 | $stepProvidersInfo[] = $statsInfo . "\n"; 96 | } 97 | $pathInfo = pathinfo($modeResult->getFileAbsolutePath()); 98 | $outputFile = str_replace( 99 | ['{dirname}', '{basename}', '{extension}', '{filename}'], 100 | [$pathInfo['dirname'], $pathInfo['basename'], $pathInfo['extension'], $pathInfo['filename']], 101 | $config['mode'][$modeResult->getName()]['outputFilename'] 102 | ); 103 | 104 | $output = '---------------------------------------------------------------------' . "\n" . 105 | "File \t\t| In : " . $modeResult->getFileAbsolutePath() . "\n"; 106 | 107 | if ($outputFile) { 108 | $output .= "\t\t| Out: " . $outputFile . "\n\n"; 109 | } 110 | 111 | if ($modeResult->getName()) { 112 | $output .= "Mode\t\t| Name: " . $modeResult->getName() . "\n" . 113 | "\t\t| Description: " . $modeResult->getDescription() . "\n" . 114 | "\t\t| Number of steps: " . $modeResult->getStepResults()->count() . "\n\n"; 115 | } 116 | if (!empty($stepProvidersInfo)) { 117 | $output .= implode("\n", $stepProvidersInfo); 118 | } 119 | $output .= "Result\t\t| "; 120 | if ($modeResult->isExecutedSuccessfully()) { 121 | $output .= 'All steps executed sucesfully. File is smaller by ' . round($modeResult->getOptimizationPercentage(), 2) . '%'; 122 | } else { 123 | if (strlen($modeResult->getInfo())) { 124 | $output .= implode("\t\t| ", array_map(function ($line) { 125 | return trim($line) . "\n"; 126 | }, explode("\n", wordwrap($modeResult->getInfo(), 100)))); 127 | } else { 128 | $output .= 'One of the steps failed. Image is not optimized.'; 129 | } 130 | } 131 | return $output . "\n"; 132 | } 133 | } 134 | -------------------------------------------------------------------------------- /Classes/Utility/FrontendProcessingUtility.php: -------------------------------------------------------------------------------- 1 | getPublicUrl(); 39 | } 40 | return !empty($GLOBALS['TSFE']->tmpl->setup['plugin.']['tx_imageopt.']['imageProcessing.']['force']) 41 | && ( 42 | empty($GLOBALS['TSFE']->tmpl->setup['plugin.']['tx_imageopt.']['imageProcessing.']['exclusion.']['regexp']) 43 | || ( 44 | !empty($GLOBALS['TSFE']->tmpl->setup['plugin.']['tx_imageopt.']['imageProcessing.']['exclusion.']['regexp']) 45 | && 46 | !preg_match( 47 | $GLOBALS['TSFE']->tmpl->setup['plugin.']['tx_imageopt.']['imageProcessing.']['exclusion.']['regexp'], 48 | $file 49 | ) 50 | ) 51 | ); 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /Classes/Utility/TemporaryFileUtility.php: -------------------------------------------------------------------------------- 1 | tempFilePrefix); 61 | if (!$this->isUnlinkTempFilesRegisteredAsShutdownFunction) { 62 | register_shutdown_function([$this, 'unlinkTempFiles']); 63 | $this->isUnlinkTempFilesRegisteredAsShutdownFunction = true; 64 | } 65 | if (file_exists($tempFilename)) { 66 | copy($originalFileAbsolutePath, $tempFilename); 67 | } 68 | return $tempFilename; 69 | } 70 | 71 | /** 72 | * Delete all temporary files of imageopt 73 | * @return void 74 | * @throws Exception 75 | */ 76 | public function unlinkTempFiles() 77 | { 78 | $varPath = Environment::getVarPath(); 79 | if (!empty($varPath)) { 80 | foreach (glob(Environment::getVarPath() . '/transient/' . $this->tempFilePrefix . '*') as $tempFile) { 81 | GeneralUtility::unlink_tempfile($tempFile); 82 | } 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /Classes/Xclass/ContentObjectRenderer.php: -------------------------------------------------------------------------------- 1 | stdWrap( 50 | $fileArray['params'], 51 | $fileArray['params.'] 52 | ) : $fileArray['params']; 53 | unset($fileArray['params.']); 54 | // Make $fileArray['params'] at least one space to count that towards hash in order to make TYPO3 to process image even if it would not be processed without this. 55 | $fileArray['params'] = $paramsValue . ' '; 56 | } 57 | return parent::getImgResource($file, $fileArray); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /Classes/Xclass/FileProcessingService.php: -------------------------------------------------------------------------------- 1 | getPublicUrl())) { 51 | // Make additionalParameters at least one space to count that towards hash in order to make TYPO3 to process image even if it would not be processed without this. 52 | $configuration['additionalParameters'] = ' '; 53 | } 54 | return parent::processFile($fileObject, $targetStorage, $taskType, $configuration); 55 | } 56 | } 57 | -------------------------------------------------------------------------------- /Configuration/Services.yaml: -------------------------------------------------------------------------------- 1 | services: 2 | _defaults: 3 | autowire: true 4 | autoconfigure: true 5 | public: false 6 | 7 | SourceBroker\Imageopt\Command\OptimizeFolderImages: 8 | tags: 9 | - name: 'console.command' 10 | command: 'imageopt:optimizefolderimages' 11 | schedulable: true 12 | 13 | SourceBroker\Imageopt\Command\OptimizeFalProcessedImages: 14 | tags: 15 | - name: 'console.command' 16 | command: 'imageopt:optimizefalprocessedimages' 17 | schedulable: true 18 | 19 | SourceBroker\Imageopt\Command\ResetOptimizationFlagForFal: 20 | tags: 21 | - name: 'console.command' 22 | command: 'imageopt:resetoptimizationflagforfal' 23 | schedulable: false 24 | 25 | 26 | SourceBroker\Imageopt\Command\ResetOptimizationFlagForFolders: 27 | tags: 28 | - name: 'console.command' 29 | command: 'imageopt:resetoptimizationflagforfolders' 30 | schedulable: false 31 | -------------------------------------------------------------------------------- /Configuration/TCA/Overrides/pages.php: -------------------------------------------------------------------------------- 1 | [ 4 | 'title' => 'LLL:EXT:imageopt/Resources/Private/Language/locallang_db.xlf:tx_imageopt_domain_model_executorresult', 5 | 'label' => 'command', 6 | 'tstamp' => 'tstamp', 7 | 'crdate' => 'crdate', 8 | 'cruser_id' => 'cruser_id', 9 | 'versioningWS' => false, 10 | 'delete' => 'deleted', 11 | 'enablecolumns' => [ 12 | 'disabled' => 'hidden', 13 | ], 14 | 'hideTable' => 1, 15 | 'searchFields' => 'size_before,size_after,command,command_output,command_status,executed_successfully,error_message', 16 | 'iconfile' => 'EXT:imageopt/Resources/Public/Icons/tx_imageopt_domain_model_executorresult.gif' 17 | ], 18 | 'types' => [ 19 | '1' => ['showitem' => '--palette--;;sizes, command, command_output, command_status, error_message, executed_successfully'], 20 | ], 21 | 'palettes' => [ 22 | 'sizes' => [ 23 | 'showitem' => 'size_before, size_after', 24 | 'canNotCollapse' => true 25 | ] 26 | ], 27 | 'columns' => [ 28 | 'hidden' => [ 29 | 'exclude' => true, 30 | 'label' => 'LLL:EXT:lang/locallang_general.xlf:LGL.hidden', 31 | 'config' => [ 32 | 'type' => 'check', 33 | 'items' => [ 34 | '1' => [ 35 | '0' => 'LLL:EXT:lang/locallang_core.xlf:labels.enabled' 36 | ] 37 | ], 38 | ], 39 | ], 40 | 'size_before' => [ 41 | 'exclude' => true, 42 | 'label' => 'LLL:EXT:imageopt/Resources/Private/Language/locallang_db.xlf:tx_imageopt_domain_model_executorresult.size_before', 43 | 'config' => [ 44 | 'type' => 'input', 45 | 'size' => 4, 46 | 'eval' => 'int', 47 | 'readOnly' => 1 48 | ] 49 | ], 50 | 'size_after' => [ 51 | 'exclude' => true, 52 | 'label' => 'LLL:EXT:imageopt/Resources/Private/Language/locallang_db.xlf:tx_imageopt_domain_model_executorresult.size_after', 53 | 'config' => [ 54 | 'type' => 'input', 55 | 'size' => 4, 56 | 'eval' => 'int', 57 | 'readOnly' => 1 58 | ] 59 | ], 60 | 'command' => [ 61 | 'exclude' => true, 62 | 'label' => 'LLL:EXT:imageopt/Resources/Private/Language/locallang_db.xlf:tx_imageopt_domain_model_executorresult.command', 63 | 'config' => [ 64 | 'type' => 'text', 65 | 'rows' => 2, 66 | 'cols' => 200, 67 | 'eval' => 'trim', 68 | 'readOnly' => 1 69 | ], 70 | ], 71 | 'command_output' => [ 72 | 'exclude' => true, 73 | 'label' => 'LLL:EXT:imageopt/Resources/Private/Language/locallang_db.xlf:tx_imageopt_domain_model_executorresult.command_output', 74 | 'config' => [ 75 | 'type' => 'text', 76 | 'rows' => 2, 77 | 'cols' => 200, 78 | 'eval' => 'trim', 79 | 'readOnly' => 1 80 | ], 81 | ], 82 | 'command_status' => [ 83 | 'exclude' => true, 84 | 'label' => 'LLL:EXT:imageopt/Resources/Private/Language/locallang_db.xlf:tx_imageopt_domain_model_executorresult.command_status', 85 | 'config' => [ 86 | 'type' => 'input', 87 | 'size' => 200, 88 | 'eval' => 'trim', 89 | 'readOnly' => 1 90 | ], 91 | ], 92 | 'error_message' => [ 93 | 'exclude' => true, 94 | 'label' => 'LLL:EXT:imageopt/Resources/Private/Language/locallang_db.xlf:tx_imageopt_domain_model_executorresult.error_message', 95 | 'config' => [ 96 | 'readOnly' => 1, 97 | 'type' => 'text', 98 | 'rows' => 2, 99 | 'cols' => 200, 100 | 'eval' => 'trim' 101 | ], 102 | ], 103 | 'executed_successfully' => [ 104 | 'exclude' => true, 105 | 'label' => 'LLL:EXT:imageopt/Resources/Private/Language/locallang_db.xlf:tx_imageopt_domain_model_executorresult.executed_successfully', 106 | 'config' => [ 107 | 'readOnly' => 1, 108 | 'type' => 'check', 109 | 'items' => [ 110 | '1' => [ 111 | '0' => 'LLL:EXT:lang/locallang_core.xlf:labels.enabled' 112 | ] 113 | ], 114 | 'default' => 0, 115 | ] 116 | ], 117 | 'provider_result' => [ 118 | 'config' => [ 119 | 'type' => 'passthrough', 120 | ], 121 | ], 122 | ], 123 | ]; 124 | -------------------------------------------------------------------------------- /Configuration/TCA/tx_imageopt_domain_model_moderesult.php: -------------------------------------------------------------------------------- 1 | [ 4 | 'title' => 'LLL:EXT:imageopt/Resources/Private/Language/locallang_db.xlf:tx_imageopt_domain_model_optimizationoptionresult', 5 | 'label' => 'file_absolute_path', 6 | 'tstamp' => 'tstamp', 7 | 'crdate' => 'crdate', 8 | 'cruser_id' => 'cruser_id', 9 | 'versioningWS' => false, 10 | 'delete' => 'deleted', 11 | 'enablecolumns' => [ 12 | 'disabled' => 'hidden', 13 | ], 14 | 'searchFields' => 'file_absolute_path,size_before,size_after,optimization_bytes,optimization_percentage,executed_successfully,info', 15 | 'iconfile' => 'EXT:imageopt/Resources/Public/Icons/tx_imageopt_domain_model_optimizationoptionresult.gif' 16 | ], 17 | 'types' => [ 18 | '1' => ['showitem' => 'file_absolute_path, --palette--;;sizes, --palette--;;optimization, info, executed_successfully, step_results'], 19 | ], 20 | 'palettes' => [ 21 | 'sizes' => [ 22 | 'showitem' => 'size_before, size_after', 23 | 'canNotCollapse' => true 24 | ], 25 | 'optimization' => [ 26 | 'showitem' => 'optimization_bytes, optimization_percentage', 27 | 'canNotCollapse' => true 28 | ] 29 | ], 30 | 'columns' => [ 31 | 'hidden' => [ 32 | 'exclude' => true, 33 | 'label' => 'LLL:EXT:lang/locallang_general.xlf:LGL.hidden', 34 | 'config' => [ 35 | 'type' => 'check', 36 | 'items' => [ 37 | '1' => [ 38 | '0' => 'LLL:EXT:lang/locallang_core.xlf:labels.enabled' 39 | ] 40 | ], 41 | ], 42 | ], 43 | 'file_absolute_path' => [ 44 | 'exclude' => true, 45 | 'label' => 'LLL:EXT:imageopt/Resources/Private/Language/locallang_db.xlf:tx_imageopt_domain_model_optimizationoptionresult.file_absolute_path', 46 | 'config' => [ 47 | 'type' => 'input', 48 | 'size' => 200, 49 | 'eval' => 'trim', 50 | 'readOnly' => 1 51 | ], 52 | ], 53 | 'size_before' => [ 54 | 'exclude' => true, 55 | 'label' => 'LLL:EXT:imageopt/Resources/Private/Language/locallang_db.xlf:tx_imageopt_domain_model_optimizationoptionresult.size_before', 56 | 'config' => [ 57 | 'type' => 'input', 58 | 'size' => 4, 59 | 'eval' => 'int', 60 | 'readOnly' => 1 61 | ], 62 | ], 63 | 'size_after' => [ 64 | 'exclude' => true, 65 | 'label' => 'LLL:EXT:imageopt/Resources/Private/Language/locallang_db.xlf:tx_imageopt_domain_model_optimizationoptionresult.size_after', 66 | 'config' => [ 67 | 'type' => 'input', 68 | 'size' => 4, 69 | 'eval' => 'int', 70 | 'readOnly' => 1 71 | ], 72 | ], 73 | 'executed_successfully' => [ 74 | 'exclude' => true, 75 | 'label' => 'LLL:EXT:imageopt/Resources/Private/Language/locallang_db.xlf:tx_imageopt_domain_model_optimizationoptionresult.executed_successfully', 76 | 'config' => [ 77 | 'type' => 'check', 78 | 'items' => [ 79 | '1' => [ 80 | '0' => 'LLL:EXT:lang/locallang_core.xlf:labels.enabled' 81 | ] 82 | ], 83 | 'default' => 0, 84 | 'readOnly' => 1 85 | ] 86 | ], 87 | 'info' => [ 88 | 'exclude' => true, 89 | 'label' => 'LLL:EXT:imageopt/Resources/Private/Language/locallang_db.xlf:tx_imageopt_domain_model_optimizationoptionresult.info', 90 | 'config' => [ 91 | 'type' => 'text', 92 | 'cols' => 200, 93 | 'rows' => 3, 94 | 'eval' => 'trim', 95 | 'readOnly' => 1, 96 | ] 97 | ], 98 | 'step_results' => [ 99 | 'exclude' => true, 100 | 'label' => 'LLL:EXT:imageopt/Resources/Private/Language/locallang_db.xlf:tx_imageopt_domain_model_optimizationoptionresult.optimization_step_results', 101 | 'config' => [ 102 | 'type' => 'inline', 103 | 'readOnly' => 1, 104 | 'foreign_table' => 'tx_imageopt_domain_model_stepresult', 105 | 'foreign_field' => 'mode_result', 106 | 'maxitems' => 9999, 107 | 'appearance' => [ 108 | 'collapseAll' => 1, 109 | 'levelLinksPosition' => 'top', 110 | 'showSynchronizationLink' => 1, 111 | 'showPossibleLocalizationRecords' => 1, 112 | 'showAllLocalizationLink' => 1 113 | ], 114 | ], 115 | ], 116 | ], 117 | ]; 118 | -------------------------------------------------------------------------------- /Configuration/TCA/tx_imageopt_domain_model_providerresult.php: -------------------------------------------------------------------------------- 1 | [ 4 | 'title' => 'LLL:EXT:imageopt/Resources/Private/Language/locallang_db.xlf:tx_imageopt_domain_model_providerresult', 5 | 'label' => 'name', 6 | 'tstamp' => 'tstamp', 7 | 'crdate' => 'crdate', 8 | 'cruser_id' => 'cruser_id', 9 | 'versioningWS' => false, 10 | 'delete' => 'deleted', 11 | 'enablecolumns' => [ 12 | 'disabled' => 'hidden', 13 | ], 14 | 'hideTable' => 1, 15 | 'searchFields' => 'name,size_before,size_after,executed_successfully,executors_results', 16 | 'iconfile' => 'EXT:imageopt/Resources/Public/Icons/tx_imageopt_domain_model_providerresult.gif' 17 | ], 18 | 'types' => [ 19 | '1' => ['showitem' => '--palette--;;sizes, --palette--;;checks, executors_results'], 20 | ], 21 | 'palettes' => [ 22 | 'sizes' => [ 23 | 'showitem' => 'size_before, size_after', 24 | 'canNotCollapse' => true 25 | ], 26 | 'checks' => [ 27 | 'showitem' => 'executed_successfully', 28 | 'canNotCollapse' => true 29 | ] 30 | ], 31 | 'columns' => [ 32 | 'hidden' => [ 33 | 'exclude' => true, 34 | 'label' => 'LLL:EXT:lang/locallang_general.xlf:LGL.hidden', 35 | 'config' => [ 36 | 'type' => 'check', 37 | 'items' => [ 38 | '1' => [ 39 | '0' => 'LLL:EXT:lang/locallang_core.xlf:labels.enabled' 40 | ] 41 | ], 42 | 'readOnly' => 1, 43 | ], 44 | ], 45 | 46 | 'name' => [ 47 | 'exclude' => true, 48 | 'label' => 'LLL:EXT:imageopt/Resources/Private/Language/locallang_db.xlf:tx_imageopt_domain_model_providerresult.name', 49 | 'config' => [ 50 | 'type' => 'input', 51 | 'size' => 30, 52 | 'eval' => 'trim', 53 | 'readOnly' => 1, 54 | ], 55 | ], 56 | 'size_before' => [ 57 | 'exclude' => true, 58 | 'label' => 'LLL:EXT:imageopt/Resources/Private/Language/locallang_db.xlf:tx_imageopt_domain_model_providerresult.size_before', 59 | 'config' => [ 60 | 'type' => 'input', 61 | 'size' => 4, 62 | 'eval' => 'int', 63 | 'readOnly' => 1 64 | ] 65 | ], 66 | 'size_after' => [ 67 | 'exclude' => true, 68 | 'label' => 'LLL:EXT:imageopt/Resources/Private/Language/locallang_db.xlf:tx_imageopt_domain_model_providerresult.size_after', 69 | 'config' => [ 70 | 'type' => 'input', 71 | 'size' => 4, 72 | 'eval' => 'int', 73 | 'readOnly' => 1 74 | ], 75 | ], 76 | 'executed_successfully' => [ 77 | 'exclude' => true, 78 | 'label' => 'LLL:EXT:imageopt/Resources/Private/Language/locallang_db.xlf:tx_imageopt_domain_model_providerresult.executed_successfully', 79 | 'config' => [ 80 | 'type' => 'check', 81 | 'items' => [ 82 | '1' => [ 83 | '0' => 'LLL:EXT:lang/locallang_core.xlf:labels.enabled' 84 | ] 85 | ], 86 | 'default' => 0, 87 | 'readOnly' => 1, 88 | ] 89 | ], 90 | 'executors_results' => [ 91 | 'exclude' => true, 92 | 'label' => 'LLL:EXT:imageopt/Resources/Private/Language/locallang_db.xlf:tx_imageopt_domain_model_providerresult.executors_results', 93 | 'config' => [ 94 | 'type' => 'inline', 95 | 'foreign_table' => 'tx_imageopt_domain_model_executorresult', 96 | 'foreign_field' => 'provider_result', 97 | 'maxitems' => 9999, 98 | 'appearance' => [ 99 | 'collapseAll' => 1, 100 | 'levelLinksPosition' => 'top', 101 | 'showSynchronizationLink' => 1, 102 | 'showPossibleLocalizationRecords' => 1, 103 | 'showAllLocalizationLink' => 1 104 | ], 105 | 'readOnly' => 1, 106 | ], 107 | 108 | ], 109 | 'step_result' => [ 110 | 'config' => [ 111 | 'type' => 'passthrough', 112 | ], 113 | ], 114 | ], 115 | ]; 116 | -------------------------------------------------------------------------------- /Configuration/TCA/tx_imageopt_domain_model_stepresult.php: -------------------------------------------------------------------------------- 1 | [ 4 | 'title' => 'LLL:EXT:imageopt/Resources/Private/Language/locallang_db.xlf:tx_imageopt_domain_model_optimizationstepresult', 5 | 'label' => 'description', 6 | 'tstamp' => 'tstamp', 7 | 'crdate' => 'crdate', 8 | 'cruser_id' => 'cruser_id', 9 | 'versioningWS' => false, 10 | 'delete' => 'deleted', 11 | 'enablecolumns' => [ 12 | 'disabled' => 'hidden', 13 | ], 14 | 'hideTable' => 1, 15 | 'searchFields' => 'description,size_before,size_after,optimization_bytes,optimization_percentage,provider_winner_name,executed_successfully,info', 16 | 'iconfile' => 'EXT:imageopt/Resources/Public/Icons/tx_imageopt_domain_model_optimizationstepresult.gif' 17 | ], 18 | 'types' => [ 19 | '1' => ['showitem' => 'description, --palette--;;sizes, --palette--;;optimization, provider_winner_name, info, executed_successfully, providers_results'], 20 | ], 21 | 'palettes' => [ 22 | 'sizes' => [ 23 | 'showitem' => 'size_before, size_after', 24 | 'canNotCollapse' => true 25 | ], 26 | 'optimization' => [ 27 | 'showitem' => 'optimization_bytes, optimization_percentage', 28 | 'canNotCollapse' => true 29 | ] 30 | ], 31 | 'columns' => [ 32 | 'hidden' => [ 33 | 'exclude' => true, 34 | 'label' => 'LLL:EXT:lang/locallang_general.xlf:LGL.hidden', 35 | 'config' => [ 36 | 'type' => 'check', 37 | 'items' => [ 38 | '1' => [ 39 | '0' => 'LLL:EXT:lang/locallang_core.xlf:labels.enabled' 40 | ] 41 | ], 42 | ], 43 | ], 44 | 'name' => [ 45 | 'exclude' => true, 46 | 'label' => 'LLL:EXT:imageopt/Resources/Private/Language/locallang_db.xlf:tx_imageopt_domain_model_optimizationstepresult.name', 47 | 'config' => [ 48 | 'type' => 'input', 49 | 'size' => 200, 50 | 'eval' => 'trim', 51 | 'readOnly' => 1 52 | ], 53 | ], 54 | 'description' => [ 55 | 'exclude' => true, 56 | 'label' => 'LLL:EXT:imageopt/Resources/Private/Language/locallang_db.xlf:tx_imageopt_domain_model_optimizationstepresult.name', 57 | 'config' => [ 58 | 'type' => 'input', 59 | 'size' => 255, 60 | 'eval' => 'trim', 61 | 'readOnly' => 1 62 | ], 63 | ], 64 | 'size_before' => [ 65 | 'exclude' => true, 66 | 'label' => 'LLL:EXT:imageopt/Resources/Private/Language/locallang_db.xlf:tx_imageopt_domain_model_optimizationstepresult.size_before', 67 | 'config' => [ 68 | 'type' => 'input', 69 | 'size' => 4, 70 | 'eval' => 'int', 71 | 'readOnly' => 1 72 | ], 73 | ], 74 | 'size_after' => [ 75 | 'exclude' => true, 76 | 'label' => 'LLL:EXT:imageopt/Resources/Private/Language/locallang_db.xlf:tx_imageopt_domain_model_optimizationstepresult.size_after', 77 | 'config' => [ 78 | 'type' => 'input', 79 | 'size' => 4, 80 | 'eval' => 'int', 81 | 'readOnly' => 1 82 | ], 83 | ], 84 | 'provider_winner_name' => [ 85 | 'exclude' => true, 86 | 'label' => 'LLL:EXT:imageopt/Resources/Private/Language/locallang_db.xlf:tx_imageopt_domain_model_optimizationstepresult.provider_winner_name', 87 | 'config' => [ 88 | 'type' => 'input', 89 | 'size' => 200, 90 | 'eval' => 'trim', 91 | 'readOnly' => 1 92 | ], 93 | ], 94 | 'executed_successfully' => [ 95 | 'exclude' => true, 96 | 'label' => 'LLL:EXT:imageopt/Resources/Private/Language/locallang_db.xlf:tx_imageopt_domain_model_optimizationstepresult.executed_successfully', 97 | 'config' => [ 98 | 'type' => 'check', 99 | 'items' => [ 100 | '1' => [ 101 | '0' => 'LLL:EXT:lang/locallang_core.xlf:labels.enabled' 102 | ] 103 | ], 104 | 'default' => 0, 105 | 'readOnly' => 1 106 | ] 107 | ], 108 | 'info' => [ 109 | 'exclude' => true, 110 | 'label' => 'LLL:EXT:imageopt/Resources/Private/Language/locallang_db.xlf:tx_imageopt_domain_model_optimizationstepresult.info', 111 | 'config' => [ 112 | 'type' => 'text', 113 | 'cols' => 200, 114 | 'rows' => 3, 115 | 'eval' => 'trim', 116 | 'readOnly' => 1, 117 | ] 118 | ], 119 | 'providers_results' => [ 120 | 'exclude' => true, 121 | 'label' => 'LLL:EXT:imageopt/Resources/Private/Language/locallang_db.xlf:tx_imageopt_domain_model_optimizationstepresult.providers_results', 122 | 'config' => [ 123 | 'type' => 'inline', 124 | 'readOnly' => 1, 125 | 'foreign_table' => 'tx_imageopt_domain_model_providerresult', 126 | 'foreign_field' => 'step_result', 127 | 'maxitems' => 9999, 128 | 'appearance' => [ 129 | 'collapseAll' => 1, 130 | 'levelLinksPosition' => 'top', 131 | 'showSynchronizationLink' => 1, 132 | 'showPossibleLocalizationRecords' => 1, 133 | 'showAllLocalizationLink' => 1 134 | ], 135 | ], 136 | 137 | ], 138 | 'mode_result' => [ 139 | 'config' => [ 140 | 'type' => 'passthrough', 141 | ], 142 | ], 143 | 144 | ], 145 | ]; 146 | -------------------------------------------------------------------------------- /Configuration/TsConfig/Page/tx_imageopt__0100.tsconfig: -------------------------------------------------------------------------------- 1 | // [LOCAL] Good standard. mozjpeg / pngquant / gifsicle + loosless finishers. 2 | 3 | 4 | tx_imageopt { 5 | mode { 6 | default { 7 | description = Saving with high quality and the same filename. 8 | step { 9 | 10 { 10 | providerType = lossyGood 11 | description = Lossy, good quality image optimisation. 12 | } 13 | 20 { 14 | providerType = lossless 15 | description = Lossless image optimisation. 16 | } 17 | } 18 | fileRegexp = .* 19 | outputFilename = {dirname}/{filename}.{extension} 20 | } 21 | webp { 22 | description = Saving under webp format and different name. 23 | step { 24 | 10 { 25 | providerType = webp 26 | description = Webp convert 27 | } 28 | } 29 | fileRegexp = \.(jpg|jpeg|png)$ 30 | outputFilename = {dirname}/{filename}.{extension}.webp 31 | } 32 | } 33 | providers { 34 | mozjpeg.enabled = 1 35 | jpegtranMozjpeg.enabled = 1 36 | pngquant.enabled = 1 37 | pngcrush.enabled = 1 38 | optipng.enabled = 1 39 | gifsicle.enabled = 1 40 | webpImagemagick.enabled = 1 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /Configuration/TsConfig/Page/tx_imageopt__0110.tsconfig: -------------------------------------------------------------------------------- 1 | // [LOCAL] Default as low quality && additional image with good quality && webp. 2 | 3 | 4 | 5 | tx_imageopt { 6 | mode { 7 | default { 8 | description = Saving with low quality and the same name. 9 | step { 10 | 10 { 11 | providerType = lossyLow 12 | description = Lossy, low quality image optimisation. 13 | } 14 | 20 { 15 | providerType = lossless 16 | description = Lossless image optimisation. 17 | } 18 | } 19 | fileRegexp = .* 20 | outputFilename = {dirname}/{filename}.{extension} 21 | } 22 | webp { 23 | description = Saving under webp format and different name. 24 | step { 25 | 10 { 26 | providerType = webp 27 | description = Webp convert 28 | } 29 | } 30 | fileRegexp = \.(jpg|jpeg|png)$ 31 | outputFilename = {dirname}/{filename}.{extension}.webp 32 | } 33 | highQuality { 34 | description = Saving with high quality and differnt name. 35 | step { 36 | 10 { 37 | providerType = lossyGood 38 | description = Lossy, good quality image optimisation. 39 | } 40 | 20 { 41 | providerType = lossless 42 | description = Lossless image optimisation. 43 | } 44 | } 45 | fileRegexp = .* 46 | outputFilename = {dirname}/{filename}-hq.{extension} 47 | } 48 | } 49 | providers { 50 | mozjpeg.enabled = 1 51 | jpegtranMozjpeg.enabled = 1 52 | pngquant.enabled = 1 53 | pngcrush.enabled = 1 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /Configuration/TsConfig/Page/tx_imageopt__0120.tsconfig: -------------------------------------------------------------------------------- 1 | // [LOCAL] Only local webp. 2 | 3 | 4 | tx_imageopt { 5 | mode { 6 | webp { 7 | description = Saving under webp format and different name. 8 | step { 9 | 10 { 10 | providerType = webp 11 | description = Webp convert 12 | } 13 | } 14 | fileRegexp = \.(jpg|jpeg|png|gif)$ 15 | outputFilename = {dirname}/{filename}.{extension}.webp 16 | } 17 | } 18 | providers { 19 | webpImagemagick.enabled = 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Configuration/TsConfig/Page/tx_imageopt__0200.tsconfig: -------------------------------------------------------------------------------- 1 | // [REMOTE] Kraken loosless. 2 | 3 | 4 | tx_imageopt { 5 | mode { 6 | default { 7 | description = Saving with Kraken loosless under the same name. 8 | step { 9 | 10 { 10 | providerType = lossless 11 | description = Kraken loosless image optimisation. 12 | } 13 | } 14 | fileRegexp = .* 15 | outputFilename = {dirname}/{filename}.{extension} 16 | } 17 | } 18 | providers { 19 | kraken { 20 | enabled = 1 21 | executors.10.api.auth.key = 22 | executors.10.api.auth.pass = 23 | } 24 | } 25 | } -------------------------------------------------------------------------------- /Configuration/TsConfig/Page/tx_imageopt__0210.tsconfig: -------------------------------------------------------------------------------- 1 | // [REMOTE] Kraken intelligent lossy. 2 | // More info: https://kraken.io/docs/lossy-optimization#lossy-image-optimization 3 | 4 | 5 | tx_imageopt { 6 | mode { 7 | default { 8 | description = Saving with Kraken intelligent loosy optimisation under the same name. 9 | step { 10 | 10 { 11 | providerType = lossyGood 12 | description = Kraken intelligent loosy image optimisation. 13 | } 14 | } 15 | fileRegexp = .* 16 | outputFilename = {dirname}/{filename}.{extension} 17 | } 18 | } 19 | providers { 20 | kraken { 21 | enabled = 1 22 | executors.10.api.auth.key = 23 | executors.10.api.auth.pass = 24 | } 25 | } 26 | } -------------------------------------------------------------------------------- /Configuration/TsConfig/Page/tx_imageopt__0220.tsconfig: -------------------------------------------------------------------------------- 1 | // [REMOTE] Kraken loosless & imagemagick webp. 2 | 3 | 4 | tx_imageopt { 5 | mode { 6 | default { 7 | description = Saving with Kraken loosless under the same name. 8 | step { 9 | 10 { 10 | providerType = lossless 11 | description = Kraken loosless image optimisation. 12 | } 13 | } 14 | fileRegexp = .* 15 | outputFilename = {dirname}/{filename}.{extension} 16 | } 17 | webp { 18 | description = Saving with webp format under different name. 19 | step { 20 | 10 { 21 | providerType = webp 22 | description = Webp convert. 23 | } 24 | } 25 | fileRegexp = \.(jpg|jpeg|png)$ 26 | outputFilename = {dirname}/{filename}.{extension}.webp 27 | } 28 | } 29 | providers { 30 | kraken { 31 | enabled = 1 32 | executors.10.api.auth.key = 33 | executors.10.api.auth.pass = 34 | typeOverride.webp.enabled = 0 35 | } 36 | webpImagemagick { 37 | enabled = 1 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Configuration/TsConfig/Page/tx_imageopt__0230.tsconfig: -------------------------------------------------------------------------------- 1 | // [REMOTE] Kraken intelligent lossy & imagemagick webp. 2 | 3 | 4 | tx_imageopt { 5 | mode { 6 | default { 7 | description = Saving with Kraken intelligent loosy optimisation under the same name. 8 | step { 9 | 10 { 10 | providerType = lossyGood 11 | description = Kraken intelligent loosy image optimisation. 12 | } 13 | } 14 | fileRegexp = .* 15 | outputFilename = {dirname}/{filename}.{extension} 16 | } 17 | webp { 18 | description = Saving with webp format under different name. 19 | step { 20 | 10 { 21 | providerType = webp 22 | description = Webp convert. 23 | } 24 | } 25 | fileRegexp = \.(jpg|jpeg|png)$ 26 | outputFilename = {dirname}/{filename}.{extension}.webp 27 | } 28 | } 29 | providers { 30 | kraken { 31 | enabled = 1 32 | executors.10.api.auth.key = 33 | executors.10.api.auth.pass = 34 | typeOverride.webp.enabled = 0 35 | } 36 | webpImagemagick { 37 | enabled = 1 38 | } 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Configuration/TypoScript/setup.typoscript: -------------------------------------------------------------------------------- 1 | plugin.tx_imageopt { 2 | imageProcessing { 3 | // Force processing of all images on frontend because imageopt should not optimize original images. 4 | force = 1 5 | exclusion { 6 | // Regexp on filepath and filename. When true this file will not be forced to be processed on frontend. 7 | // Example /animation.*\.gif/ -> do not force gif files that have animation in name or folder name. 8 | // Example /\.gif/ -> do not force gif files 9 | regexp = 10 | } 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Documentation/Images/ExecutorResultExample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourcebroker/imageopt/281bd2f463b4c3b95b9fb07b926a5f36adf502b1/Documentation/Images/ExecutorResultExample.png -------------------------------------------------------------------------------- /Documentation/Images/OutputCliExample.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourcebroker/imageopt/281bd2f463b4c3b95b9fb07b926a5f36adf502b1/Documentation/Images/OutputCliExample.png -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | TYPO3 Extension imageopt 2 | ======================== 3 | 4 | .. image:: https://travis-ci.org/sourcebroker/imageopt.svg?branch=master 5 | :target: https://travis-ci.org/sourcebroker/imageopt 6 | 7 | .. image:: https://poser.pugx.org/sourcebroker/imageopt/license 8 | :target: https://packagist.org/packages/sourcebroker/imageopt 9 | 10 | .. contents:: :local: 11 | 12 | What does it do? 13 | ---------------- 14 | 15 | This extension optimize images resized by TYPO3 so they will take less space, 16 | page will be downloaded faster and Google PageSpeed Insights score will get higher. 17 | 18 | Example CLI output: 19 | 20 | .. image:: Documentation/Images/OutputCliExample.png 21 | :width: 100% 22 | 23 | TYPO3 backend - "Executor Result" record example: 24 | 25 | .. image:: Documentation/Images/ExecutorResultExample.png 26 | :width: 100% 27 | 28 | Features 29 | -------- 30 | 31 | - Its safe as the original images, for example in folder ``fileadmin/``, ``uploads/`` are not optmized! 32 | Only already resized images are optmized, so for FAL that would be files form ``_processed_/`` folders and for 33 | ``uploads/`` it will be files from ``typo3temp/assets/images``. Imageopt can force images to be processed so 34 | in other words you will not find any image in HTML that links directly to original images in ``/fileadmin/`` 35 | or ``/uploads/``. 36 | 37 | - Support for following local binaries providers: 38 | 39 | * for png - pngquant, optipng, pngcrush, 40 | * for gif - gifsicle, 41 | * for jpeg - mozjpeg, jpegoptim, jpegtran. 42 | 43 | - Support for following remote providers: 44 | 45 | * kraken.io, 46 | * imageoptim.com, 47 | * tinypng.com. 48 | 49 | - Own providers can be registered with page TSconfig. 50 | 51 | - Can create file variants with diffent name. For example optimize as webp and save under {filename}.{extension}.webp 52 | 53 | 54 | Installation 55 | ------------ 56 | 57 | 1) Install using composer: 58 | 59 | :: 60 | 61 | composer require sourcebroker/imageopt 62 | 63 | 64 | 65 | Configuration 66 | ------------- 67 | 68 | 1) Open main Template record and add "imageopt" in tab "Includes" -> field "Include static (from extensions)" 69 | 70 | 2) Open homepage properties and choose one of predefined modes (or create own) 71 | 72 | 3) If you choosed Kraken in predefined mode then you need to enter the key / pass pair in PageTS. 73 | 74 | :: 75 | 76 | tx_imageopt { 77 | providers { 78 | kraken { 79 | executors.10.api.auth.key = 2dae79a5813bb19eda29cc0cb4c9d39c 80 | executors.10.api.auth.pass = 87e06b68c69f71afbf5c1730b49f48e5c26db24a 81 | } 82 | } 83 | } 84 | 85 | 86 | Usage 87 | ----- 88 | 89 | 1) Make a direct cli command run to optimize all existing images at once for first time. 90 | 91 | a) For FAL processed images: 92 | :: 93 | 94 | php typo3/sysext/core/bin/typo3 imageopt:optimizefalprocessedimages --numberOfImagesToProcess=999 95 | 96 | b) For folder processed images. 97 | :: 98 | 99 | php typo3/sysext/core/bin/typo3 imageopt:optimizefolderimages --numberOfImagesToProcess=999 100 | 101 | Command "imageopt:optimizefolderimages" will optimize images in following folders: 102 | 103 | - typo3temp/pics/ 104 | - typo3temp/GB/ 105 | - typo3temp/assets/images/ 106 | 107 | 2) For all images which will be processed in future set up scheduler job. 108 | 109 | 110 | Configuration for frontend image processing 111 | ------------------------------------------- 112 | 113 | As already stated imageopt extension offers processing of all images even if the processing is not needed (for example because the size of original image is the same as desired image). Its good and safe because original images in folder ``fileadmin/``, ``uploads/`` are not optmized so in case of wrong optimisation nothing will be destroyed! Only already resized images are optmized, so for FAL that would be files form ``_processed_/`` folders and for ``uploads/`` it will be ``typo3temp/assets/images``. 114 | 115 | To enable this feature you need to open main Template record and add "imageopt" in tab "Includes" -> "Include static (from extensions)". If you do not enable this feature then it can be that not all images will be optimized as part of them will be used directly from ``fileadmin/`` or ``uploads/`` folders. 116 | 117 | The Typoscript added by imageopt is: 118 | 119 | :: 120 | 121 | plugin.tx_imageopt { 122 | imageProcessing { 123 | // Force processing of all images on frontend because imageopt should not optimize original images. 124 | force = 1 125 | exclusion { 126 | // Regexp on filepath and filename. When true this file will not be forced to be processed on frontend. 127 | // Example /animation.*\.gif/ -> do not force gif files that have animation in name or folder name. 128 | // Example /\.gif/ -> do not force gif files 129 | regexp = 130 | } 131 | } 132 | } 133 | 134 | As you see you can use ``plugin.tx_imageopt.exclusion.regexp`` to exclude files which will be not forced to be processed (so the original version will be used). This is handy for example for gif animations (which are not supported to be processed by TYPO3). You can use ``plugin.tx_imageopt.exclusion.regexp`` also to not process images that you think are arleady optimized enough. 135 | 136 | 137 | Technical notes 138 | --------------- 139 | 140 | * For FAL only files that are in "sys_file_processedfile" are optimized. Table "sys_file_processedfile" 141 | has been extended with field "tx_imageopt_executed_successfully". If file has been optimised then the field 142 | "tx_imageopt_executed_successfully" is set to 1. 143 | 144 | You can reset the "tx_imageopt_executed_successfully" flag with command: 145 | :: 146 | 147 | php typo3/sysext/core/bin/typo3 imageopt:resetoptimizationflagforfal 148 | 149 | This can be handy for testing purposes. 150 | 151 | * If you optimize files from folders then if file has been optimized it gets "executed" persmission bit. So for most 152 | cases its 644 on the beginning and 744 after optimization. The "execution" bit is the way script knows which files 153 | has been optimized and which one still needs. 154 | 155 | You can reset the "executed" bit for folders declared in "tx_imageopt.directories" with command: 156 | :: 157 | 158 | php typo3/sysext/core/bin/typo3 imageopt:resetoptimizationflagforfolders 159 | 160 | This can be handy for testing purposes. 161 | 162 | 163 | Changelog 164 | --------- 165 | 166 | See https://github.com/sourcebroker/imageopt/blob/master/CHANGELOG.rst 167 | -------------------------------------------------------------------------------- /Resources/Private/.htaccess: -------------------------------------------------------------------------------- 1 | # Apache < 2.3 2 | 3 | Order allow,deny 4 | Deny from all 5 | Satisfy All 6 | 7 | 8 | # Apache >= 2.3 9 | 10 | Require all denied 11 | 12 | -------------------------------------------------------------------------------- /Resources/Private/Language/locallang.xlf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 | 7 | Provider Result 8 | 9 | 10 | Name 11 | 12 | 13 | Size Before 14 | 15 | 16 | Size After 17 | 18 | 19 | Executed Successfully 20 | 21 | 22 | Executors Results 23 | 24 | 25 | Executor Result 26 | 27 | 28 | Size Before 29 | 30 | 31 | Size After 32 | 33 | 34 | Command 35 | 36 | 37 | Command Output 38 | 39 | 40 | Command Status 41 | 42 | 43 | Executed Successfully 44 | 45 | 46 | Error Message 47 | 48 | 49 | Optimization Option Result 50 | 51 | 52 | File Absolute Path 53 | 54 | 55 | Size Before 56 | 57 | 58 | Size After 59 | 60 | 61 | Optimization Bytes 62 | 63 | 64 | Optimization Percentage 65 | 66 | 67 | Provider Winner Name 68 | 69 | 70 | Executed Successfully 71 | 72 | 73 | Optimization Step Results 74 | 75 | 76 | Optimization Step Result 77 | 78 | 79 | Size Before 80 | 81 | 82 | Size After 83 | 84 | 85 | Optimization Bytes 86 | 87 | 88 | Optimization Percentage 89 | 90 | 91 | Provider Winner Name 92 | 93 | 94 | Executed Successfully 95 | 96 | 97 | Providers Results 98 | 99 | 100 | 101 | 102 | -------------------------------------------------------------------------------- /Resources/Private/Language/locallang_csh_tx_imageopt_domain_model_executorresult.xlf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 | 7 | sizeBefore 8 | 9 | 10 | sizeAfter 11 | 12 | 13 | command 14 | 15 | 16 | commandOutput 17 | 18 | 19 | commandStatus 20 | 21 | 22 | executedSuccessfully 23 | 24 | 25 | errorMessage 26 | 27 | 28 | 29 | -------------------------------------------------------------------------------- /Resources/Private/Language/locallang_csh_tx_imageopt_domain_model_optimizationoptionresult.xlf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 | 7 | fileAbsolutePath 8 | 9 | 10 | sizeBefore 11 | 12 | 13 | sizeAfter 14 | 15 | 16 | optimizationBytes 17 | 18 | 19 | optimizationPercentage 20 | 21 | 22 | providerWinnerName 23 | 24 | 25 | executedSuccessfully 26 | 27 | 28 | optimizationStepResults 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /Resources/Private/Language/locallang_csh_tx_imageopt_domain_model_optimizationstepresult.xlf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 | 7 | sizeBefore 8 | 9 | 10 | sizeAfter 11 | 12 | 13 | optimizationBytes 14 | 15 | 16 | optimizationPercentage 17 | 18 | 19 | providerWinnerName 20 | 21 | 22 | executedSuccessfully 23 | 24 | 25 | providersResults 26 | 27 | 28 | 29 | 30 | -------------------------------------------------------------------------------- /Resources/Private/Language/locallang_csh_tx_imageopt_domain_model_providerresult.xlf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 | 7 | Provider name 8 | 9 | 10 | File size before optimization 11 | 12 | 13 | File size after optimization 14 | 15 | 16 | Boolean if all executors was succesfully finished 17 | 18 | 19 | Boolean if provider is winner 20 | 21 | 22 | executorsResults 23 | 24 | 25 | 26 | -------------------------------------------------------------------------------- /Resources/Private/Language/locallang_db.xlf: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 |
5 | 6 | 7 | Provider Result 8 | 9 | 10 | Name 11 | 12 | 13 | Size Before 14 | 15 | 16 | Size After 17 | 18 | 19 | Executed Successfully 20 | 21 | 22 | Executors Results 23 | 24 | 25 | Executor Result 26 | 27 | 28 | Size Before 29 | 30 | 31 | Size After 32 | 33 | 34 | Command 35 | 36 | 37 | Command Output 38 | 39 | 40 | Command Status 41 | 42 | 43 | Executed Successfully 44 | 45 | 46 | Error Message 47 | 48 | 49 | Image Optimization Result 50 | 51 | 52 | File Absolute Path 53 | 54 | 55 | Size Before 56 | 57 | 58 | Size After 59 | 60 | 61 | Optimization Bytes 62 | 63 | 64 | Optimization Percentage 65 | 66 | 67 | Provider Winner Name 68 | 69 | 70 | Executed Successfully 71 | 72 | 73 | Image Optimization Step Results 74 | 75 | 76 | Info 77 | 78 | 79 | Image Optimization Step Result 80 | 81 | 82 | Name 83 | 84 | 85 | Description 86 | 87 | 88 | Size Before 89 | 90 | 91 | Size After 92 | 93 | 94 | Optimization Bytes 95 | 96 | 97 | Optimization Percentage 98 | 99 | 100 | Provider Winner Name 101 | 102 | 103 | Executed Successfully 104 | 105 | 106 | Providers Results 107 | 108 | 109 | Info 110 | 111 | 112 | Optimize FAL processed images 113 | 114 | 115 | Optimize FAL processed images. 116 | 117 | 118 | Optimize folders images 119 | 120 | 121 | Optimize images generated from /uploads resources and stored in /typo3temp. You can define own folders also. 122 | 123 | 124 | 125 | 126 | -------------------------------------------------------------------------------- /Resources/Public/Icons/Extension.svg: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | -------------------------------------------------------------------------------- /Resources/Public/Icons/relation.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourcebroker/imageopt/281bd2f463b4c3b95b9fb07b926a5f36adf502b1/Resources/Public/Icons/relation.gif -------------------------------------------------------------------------------- /Resources/Public/Icons/tx_imageopt_domain_model_executorresult.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourcebroker/imageopt/281bd2f463b4c3b95b9fb07b926a5f36adf502b1/Resources/Public/Icons/tx_imageopt_domain_model_executorresult.gif -------------------------------------------------------------------------------- /Resources/Public/Icons/tx_imageopt_domain_model_optimizationoptionresult.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourcebroker/imageopt/281bd2f463b4c3b95b9fb07b926a5f36adf502b1/Resources/Public/Icons/tx_imageopt_domain_model_optimizationoptionresult.gif -------------------------------------------------------------------------------- /Resources/Public/Icons/tx_imageopt_domain_model_optimizationstepresult.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourcebroker/imageopt/281bd2f463b4c3b95b9fb07b926a5f36adf502b1/Resources/Public/Icons/tx_imageopt_domain_model_optimizationstepresult.gif -------------------------------------------------------------------------------- /Resources/Public/Icons/tx_imageopt_domain_model_providerresult.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourcebroker/imageopt/281bd2f463b4c3b95b9fb07b926a5f36adf502b1/Resources/Public/Icons/tx_imageopt_domain_model_providerresult.gif -------------------------------------------------------------------------------- /Tests/Fixture/Unit/OptimizeImageService/mountains.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourcebroker/imageopt/281bd2f463b4c3b95b9fb07b926a5f36adf502b1/Tests/Fixture/Unit/OptimizeImageService/mountains.gif -------------------------------------------------------------------------------- /Tests/Fixture/Unit/OptimizeImageService/mountains.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourcebroker/imageopt/281bd2f463b4c3b95b9fb07b926a5f36adf502b1/Tests/Fixture/Unit/OptimizeImageService/mountains.jpg -------------------------------------------------------------------------------- /Tests/Fixture/Unit/OptimizeImageService/mountains.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/sourcebroker/imageopt/281bd2f463b4c3b95b9fb07b926a5f36adf502b1/Tests/Fixture/Unit/OptimizeImageService/mountains.png -------------------------------------------------------------------------------- /Tests/Unit/Configuration/ConfiguratorTest.php: -------------------------------------------------------------------------------- 1 | getMockBuilder(Configurator::class) 26 | ->setMethods(null) 27 | ->getMock(); 28 | $configurator->setConfig($this->staticTsConfig()); 29 | 30 | $this->assertEquals($expected, $configurator->getOption($given)); 31 | } 32 | 33 | /** 34 | * Data provider for configuratorOptionsAreCorrectlyReturned 35 | * 36 | * @return array 37 | */ 38 | public function configuratorOptionsAreCorrectlyReturnedDataProvider() 39 | { 40 | return [ 41 | 'nonWorkingConfigurationNullConfig' => [ 42 | null, 43 | null 44 | ], 45 | 'nonWorkingConfigurationEmptyConfig' => [ 46 | '', 47 | null 48 | ], 49 | 'nonWorkingConfigurationArrayConfig' => [ 50 | ['value'], 51 | null 52 | ], 53 | 'nonWorkingConfigurationNotExistingConfig' => [ 54 | 'notExistingOption', 55 | null 56 | ], 57 | 'workingConfigurationFirstLevelString' => [ 58 | 'option1', 59 | 'value' 60 | ], 61 | 'workingConfigurationFirstLevelArray' => [ 62 | 'option2', 63 | ['option2Sub' => 'value'] 64 | ], 65 | 'workingConfigurationSecondLevelString' => [ 66 | 'option2.option2Sub', 67 | 'value' 68 | ], 69 | 'workingConfigurationSecondLevelArray' => [ 70 | 'option3.option3Sub', 71 | [ 72 | 'option3SubSub1' => 'value1', 73 | 'option3SubSub2' => 'value2' 74 | ] 75 | ], 76 | ]; 77 | } 78 | 79 | public function staticTsConfig() 80 | { 81 | return [ 82 | 'option1' => 'value', 83 | 'option2' => [ 84 | 'option2Sub' => 'value' 85 | ], 86 | 'option3' => [ 87 | 'option3Sub' => [ 88 | 'option3SubSub1' => 'value1', 89 | 'option3SubSub2' => 'value2' 90 | ] 91 | ] 92 | ]; 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /Tests/Unit/Service/OptimizeImageServiceTest.php: -------------------------------------------------------------------------------- 1 | typo3WebRoot = realpath(__DIR__ . '/../../../.Build/Web/'); 29 | parent::setUp(); 30 | } 31 | 32 | protected function tearDown() : void 33 | { 34 | foreach (glob($this->typo3WebRoot . '/typo3temp/tx_imageopt*') as $tempFile) { 35 | unlink($tempFile); 36 | } 37 | foreach (glob($this->typo3WebRoot . '/typo3temp/var/transient/tx_imageopt*') as $tempFile) { 38 | unlink($tempFile); 39 | } 40 | } 41 | 42 | /** 43 | * imageIsOptimized 44 | * 45 | * @dataProvider imageIsOptimizedDataProvider 46 | * @test 47 | * @param $image 48 | * @throws Exception 49 | */ 50 | public function allProvidersSuccessful($image) 51 | { 52 | fwrite(STDOUT, "\n" . 'TEST if all providers was executed succesfully.' . "\n"); 53 | 54 | /** @var \SourceBroker\Imageopt\Service\OptimizeImageService $optimizeImageService */ 55 | $optimizeImageService = $this->getMockBuilder(OptimizeImageService::class) 56 | ->setConstructorArgs([$this->pluginConfig()]) 57 | ->setMethods(null) 58 | ->getMock(); 59 | 60 | $temporaryFileUtility = GeneralUtility::makeInstance(TemporaryFileUtility::class); 61 | $orignalImagePath = $this->typo3WebRoot . '/typo3conf/ext/imageopt/Tests/Fixture/Unit/OptimizeImageService/' . $image; 62 | $imageForTesting = $temporaryFileUtility->createTemporaryCopy($orignalImagePath); 63 | if (is_readable($imageForTesting)) { 64 | /** @var ModeResult[] $optimizationResults */ 65 | $optimizationResults = $optimizeImageService->optimize($imageForTesting); 66 | 67 | foreach ($optimizationResults as $optimizationResult) { 68 | fwrite(STDOUT, CliDisplayUtility::displayOptionResult($optimizationResult, $this->pluginConfig())); 69 | } 70 | 71 | /** @var ModeResult $defaultResult */ 72 | $defaultResult = isset($optimizationResults['default']) 73 | ? $optimizationResults['default'] 74 | : reset($optimizationResults); 75 | 76 | $this->assertEquals( 77 | $defaultResult->getStepResults()->count(), 78 | $defaultResult->getExecutedSuccessfullyNum() 79 | ); 80 | } else { 81 | throw new Exception('Image for testing is not existing:' . $imageForTesting); 82 | } 83 | } 84 | 85 | /** 86 | * imageIsOptimized 87 | * @test 88 | * @dataProvider imageIsOptimizedDataProvider 89 | * @param $image 90 | * @throws Exception 91 | * @internal param $winner 92 | */ 93 | public function imageIsOptimized($image) 94 | { 95 | fwrite(STDOUT, "\n" . 'TEST has been optimized.' . "\n"); 96 | 97 | /** @var \SourceBroker\Imageopt\Service\OptimizeImageService $optimizeImageService */ 98 | $optimizeImageService = $this->getMockBuilder(OptimizeImageService::class) 99 | ->setConstructorArgs([$this->pluginConfig()]) 100 | ->setMethods(null) 101 | ->getMock(); 102 | 103 | $temporaryFileUtility = GeneralUtility::makeInstance(TemporaryFileUtility::class); 104 | $orignalImagePath = $this->typo3WebRoot . '/typo3conf/ext/imageopt/Tests/Fixture/Unit/OptimizeImageService/' . $image; 105 | $imageForTesting = $temporaryFileUtility->createTemporaryCopy($orignalImagePath); 106 | if (is_readable($imageForTesting)) { 107 | $originalFileSize = filesize($imageForTesting); 108 | /** @var ModeResult[] $optimizationResults */ 109 | $optimizationResults = $optimizeImageService->optimize($imageForTesting); 110 | 111 | foreach ($optimizationResults as $optimizationResult) { 112 | fwrite(STDOUT, CliDisplayUtility::displayOptionResult($optimizationResult, $this->pluginConfig())); 113 | } 114 | 115 | $defaultOptimizationResult = isset($optimizationResults['default']) 116 | ? $optimizationResults['default'] 117 | : reset($optimizationResults); 118 | 119 | $this->assertGreaterThan($defaultOptimizationResult->getSizeAfter(), $originalFileSize); 120 | } else { 121 | throw new Exception('Image for testing is not existing:' . $imageForTesting); 122 | } 123 | } 124 | 125 | /** 126 | * Data provider for imageIsOptimized 127 | * 128 | * @return array 129 | */ 130 | public function imageIsOptimizedDataProvider() 131 | { 132 | return [ 133 | 'Test jpeg file resize' => [ 134 | 'mountains.jpg', 135 | ], 136 | 'Test png file resize' => [ 137 | 'mountains.png', 138 | ], 139 | 'Test gif file resize' => [ 140 | 'mountains.gif', 141 | ], 142 | ]; 143 | } 144 | 145 | /** 146 | * Return static config for module. 147 | * 148 | * @return array 149 | * @throws Exception 150 | */ 151 | public function pluginConfig() 152 | { 153 | $configurator = GeneralUtility::makeInstance(Configurator::class); 154 | $typoscriptParser = GeneralUtility::makeInstance(TypoScriptParser::class); 155 | $typoscriptParser->parse( 156 | file_get_contents(realpath(__DIR__ . '/../../../Configuration/TsConfig/Page/tx_imageopt.tsconfig')) . "\n" . 157 | file_get_contents(realpath(__DIR__ . '/../../../Configuration/TsConfig/Page/tx_imageopt__0100.tsconfig')) 158 | ); 159 | 160 | $rawConfig = GeneralUtility::makeInstance(\TYPO3\CMS\Core\TypoScript\TypoScriptService::class) 161 | ->convertTypoScriptArrayToPlainArray($typoscriptParser->setup)['tx_imageopt']; 162 | $rawConfig['providersDefault']['enabled'] = 1; 163 | if (file_exists(__DIR__ . '/../../../.env')) { 164 | $dotenv = new Dotenv(); 165 | $dotenv->load(__DIR__ . '/../../../.env'); 166 | } 167 | 168 | $envConfig = []; 169 | foreach ($_ENV as $key => $value) { 170 | if (strpos($key, 'tx_imageopt__') === 0) { 171 | $envConfig[substr($key, 13)] = $value; 172 | } 173 | } 174 | foreach ($_SERVER as $key => $value) { 175 | if (strpos($key, 'tx_imageopt__') === 0) { 176 | $envConfig[substr($key, 13)] = $value; 177 | } 178 | } 179 | foreach ($envConfig as $name => $value) { 180 | $plainConfig = explode('__', $name); 181 | $plainConfig[] = $value; 182 | $nestedConfig = ArrayUtility::plainToNested($plainConfig); 183 | \TYPO3\CMS\Core\Utility\ArrayUtility::mergeRecursiveWithOverrule($rawConfig, $nestedConfig, false); 184 | } 185 | $configurator->setConfig($rawConfig); 186 | $configurator->init(); 187 | return $configurator->getConfig(); 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "sourcebroker/imageopt", 3 | "license": [ 4 | "GPL-2.0-or-later" 5 | ], 6 | "type": "typo3-cms-extension", 7 | "description": "Optimize images created/resized by TYPO3 so they take less space. Safe as it does not optimize original images.", 8 | "require": { 9 | "typo3/cms-core": "^10.4 || ^11.5" 10 | }, 11 | "require-dev": { 12 | "friendsofphp/php-cs-fixer": "^2.0", 13 | "nimut/testing-framework": "^5.0", 14 | "symfony/dotenv": "^5.0" 15 | }, 16 | "suggest": { 17 | "typo3/cms-scheduler": "^10.4 || ^11.5" 18 | }, 19 | "autoload": { 20 | "psr-4": { 21 | "SourceBroker\\Imageopt\\": "Classes/" 22 | } 23 | }, 24 | "autoload-dev": { 25 | "psr-4": { 26 | "SourceBroker\\Imageopt\\Tests\\": "Tests" 27 | } 28 | }, 29 | "config": { 30 | "vendor-dir": ".Build/vendor", 31 | "bin-dir": ".Build/bin", 32 | "allow-plugins": { 33 | "typo3/cms-composer-installers": true, 34 | "typo3/class-alias-loader": true 35 | } 36 | }, 37 | "extra": { 38 | "typo3/cms": { 39 | "extension-key": "imageopt", 40 | "cms-package-dir": "{$vendor-dir}/typo3/cms", 41 | "web-dir": ".Build/Web" 42 | } 43 | }, 44 | "scripts": { 45 | "phpunit-test": "TYPO3_PATH_ROOT=.Build/Web/ ./.Build/bin/phpunit -c .Build/vendor/nimut/testing-framework/res/Configuration/UnitTests.xml Tests/Unit/", 46 | "post-autoload-dump": [ 47 | "mkdir -p .Build/Web/typo3conf/ext/", 48 | "[ -L .Build/Web/typo3conf/ext/imageopt ]|| ln -snvf ../../../../. .Build/Web/typo3conf/ext/imageopt" 49 | ] 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /ext_emconf.php: -------------------------------------------------------------------------------- 1 | 'Optimize images created/resized by TYPO3', 13 | 'description' => 'Optimize images created/resized by TYPO3 so they take less space. Safe as it does not optimize original images.', 14 | 'category' => 'be', 15 | 'version' => '7.0.1', 16 | 'state' => 'stable', 17 | 'uploadfolder' => false, 18 | 'createDirs' => '', 19 | 'clearcacheonload' => false, 20 | 'author' => 'Inscript Team', 21 | 'author_email' => 'office@inscript.dev', 22 | 'author_company' => 'Inscript', 23 | 'constraints' => 24 | [ 25 | 'depends' => 26 | [ 27 | 'typo3' => '10.4.0-11.5.99', 28 | ], 29 | 'conflicts' => 30 | [], 31 | 'suggests' => 32 | [], 33 | ], 34 | ]; 35 | -------------------------------------------------------------------------------- /ext_localconf.php: -------------------------------------------------------------------------------- 1 | SourceBroker\Imageopt\Xclass\FileProcessingService::class 9 | ]; 10 | $GLOBALS['TYPO3_CONF_VARS']['SYS']['Objects'][TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::class] = [ 11 | 'className' => SourceBroker\Imageopt\Xclass\ContentObjectRenderer::class 12 | ]; 13 | -------------------------------------------------------------------------------- /ext_tables.php: -------------------------------------------------------------------------------- 1 |