├── .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 |
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 |