diff --git a/cross/php84/Makefile b/cross/php84/Makefile new file mode 100644 index 00000000000..98135020fce --- /dev/null +++ b/cross/php84/Makefile @@ -0,0 +1,100 @@ +PKG_NAME = php +PKG_VERS = 8.4.15 +PKG_EXT = tar.xz +PKG_DIST_NAME = $(PKG_NAME)-$(PKG_VERS).$(PKG_EXT) +PKG_DIST_SITE = https://www.php.net/distributions +PKG_DIR = $(PKG_NAME)-$(PKG_VERS) + +# Core dependencies +DEPENDS = cross/zlib +DEPENDS += cross/openssl3 +DEPENDS += cross/libxml2 +DEPENDS += cross/sqlite +DEPENDS += cross/curl +DEPENDS += cross/oniguruma +DEPENDS += cross/libzip +DEPENDS += cross/icu +DEPENDS += cross/freetype +DEPENDS += cross/libpng +DEPENDS += cross/libjpeg +DEPENDS += cross/libgd +DEPENDS += cross/bzip2 +DEPENDS += cross/libsodium +DEPENDS += cross/readline +DEPENDS += cross/ncurses +DEPENDS += cross/gmp +DEPENDS += cross/libffi + +HOMEPAGE = https://www.php.net +COMMENT = PHP Hypertext Preprocessor +LICENSE = PHP-3.01 + +GNU_CONFIGURE = 1 +ADDITIONAL_CFLAGS = -I$(STAGING_INSTALL_PREFIX)/include +ADDITIONAL_LDFLAGS = -L$(STAGING_INSTALL_PREFIX)/lib + +# PHP Configure arguments +CONFIGURE_ARGS = --prefix=$(INSTALL_PREFIX) +CONFIGURE_ARGS += --sysconfdir=$(INSTALL_PREFIX)/etc +CONFIGURE_ARGS += --with-config-file-path=$(INSTALL_PREFIX)/etc +CONFIGURE_ARGS += --with-config-file-scan-dir=$(INSTALL_PREFIX)/etc/conf.d + +# SAPI modules +CONFIGURE_ARGS += --enable-fpm +CONFIGURE_ARGS += --disable-cgi +CONFIGURE_ARGS += --enable-cli + +# Core extensions (built-in) +CONFIGURE_ARGS += --enable-bcmath +CONFIGURE_ARGS += --enable-calendar +CONFIGURE_ARGS += --enable-ctype +CONFIGURE_ARGS += --enable-exif +CONFIGURE_ARGS += --enable-fileinfo +CONFIGURE_ARGS += --enable-filter +CONFIGURE_ARGS += --enable-ftp +CONFIGURE_ARGS += --enable-intl +CONFIGURE_ARGS += --enable-mbstring +CONFIGURE_ARGS += --enable-opcache +CONFIGURE_ARGS += --enable-pcntl +CONFIGURE_ARGS += --enable-pdo +CONFIGURE_ARGS += --enable-phar +CONFIGURE_ARGS += --enable-posix +CONFIGURE_ARGS += --enable-session +CONFIGURE_ARGS += --enable-shmop +CONFIGURE_ARGS += --enable-soap +CONFIGURE_ARGS += --enable-sockets +CONFIGURE_ARGS += --enable-sysvmsg +CONFIGURE_ARGS += --enable-sysvsem +CONFIGURE_ARGS += --enable-sysvshm +CONFIGURE_ARGS += --enable-tokenizer +CONFIGURE_ARGS += --enable-xml + +# Extensions with dependencies +CONFIGURE_ARGS += --with-bz2=$(STAGING_INSTALL_PREFIX) +CONFIGURE_ARGS += --with-curl=$(STAGING_INSTALL_PREFIX) +CONFIGURE_ARGS += --with-ffi=$(STAGING_INSTALL_PREFIX) +CONFIGURE_ARGS += --with-freetype=$(STAGING_INSTALL_PREFIX) +CONFIGURE_ARGS += --with-gd=$(STAGING_INSTALL_PREFIX) +CONFIGURE_ARGS += --enable-gd +CONFIGURE_ARGS += --with-jpeg=$(STAGING_INSTALL_PREFIX) +CONFIGURE_ARGS += --with-gmp=$(STAGING_INSTALL_PREFIX) +CONFIGURE_ARGS += --with-libxml=$(STAGING_INSTALL_PREFIX) +CONFIGURE_ARGS += --with-mhash +CONFIGURE_ARGS += --with-openssl=$(STAGING_INSTALL_PREFIX) +CONFIGURE_ARGS += --with-readline=$(STAGING_INSTALL_PREFIX) +CONFIGURE_ARGS += --with-sodium=$(STAGING_INSTALL_PREFIX) +CONFIGURE_ARGS += --with-zip=$(STAGING_INSTALL_PREFIX) +CONFIGURE_ARGS += --with-zlib=$(STAGING_INSTALL_PREFIX) + +# Database extensions +CONFIGURE_ARGS += --enable-mysqlnd +CONFIGURE_ARGS += --with-mysqli=mysqlnd +CONFIGURE_ARGS += --with-pdo-mysql=mysqlnd +CONFIGURE_ARGS += --with-pdo-sqlite=$(STAGING_INSTALL_PREFIX) +CONFIGURE_ARGS += --with-sqlite3=$(STAGING_INSTALL_PREFIX) + +# Disable problematic features for cross-compilation +CONFIGURE_ARGS += --disable-phpdbg +CONFIGURE_ARGS += --without-valgrind + +include ../../mk/spksrc.cross-cc.mk diff --git a/cross/php84/digests b/cross/php84/digests new file mode 100644 index 00000000000..f13d64f891c --- /dev/null +++ b/cross/php84/digests @@ -0,0 +1,3 @@ +php-8.4.15.tar.xz SHA256 a060684f614b8344f9b34c334b6ba8db1177555997edb5b1aceab0a4b807da7e +php-8.4.15.tar.xz SHA1 2ca7fcd49bcc28391fa0083e09575ad50d2110dc +php-8.4.15.tar.xz MD5 9b766937483f4288ddeca601b15c0277 diff --git a/spk/php84/EXTENSIONS.md b/spk/php84/EXTENSIONS.md new file mode 100644 index 00000000000..f829fe8ea59 --- /dev/null +++ b/spk/php84/EXTENSIONS.md @@ -0,0 +1,357 @@ +# PHP 8.4.15 Extensions Documentation + +**Package**: php84-8.4.15 +**Architecture**: Synology DSM 7.2+ (geminilake) +**Total Extensions**: 100 + +## Overview + +This SPK package includes 100 PHP extensions organized into 15 categories. Extensions can be enabled/disabled via: +- **Installation wizard**: Select profiles or individual extensions during installation +- **Extension Manager**: DSM application accessible via Package Center > Open + +--- + +## Categories + +| Category | Name | Extensions | Description | +|----------|------|------------|-------------| +| core | Core | 7 | Essential PHP extensions | +| database | Base de données | 13 | Database connectivity | +| cache | Cache | 3 | Caching and serialization | +| network | Réseau | 7 | Network protocols | +| text | Texte | 4 | Encoding and i18n | +| xml | XML | 6 | XML parsing | +| compression | Compression | 6 | Data compression | +| image | Image | 2 | Image processing | +| crypto | Crypto | 4 | Cryptography | +| math | Math | 3 | Mathematical operations | +| system | Système | 9 | System interaction | +| debug | Debug | 6 | Development tools | +| data | Données | 9 | Data formats | +| async | Async | 3 | Asynchronous programming | +| misc | Autres | 18 | Miscellaneous | + +--- + +## Extensions by Category + +### Core (7 extensions) + +Essential extensions enabled by default for most PHP applications. + +| Extension | File | Default | Description | +|-----------|------|---------|-------------| +| **OPcache** | opcache.so | Yes | Bytecode cache for improved performance | +| **Session** | session.so | Yes | Session handling support | +| **Filter** | filter.so | Yes | Data filtering and validation | +| **Fileinfo** | fileinfo.so | Yes | File type detection | +| **Tokenizer** | tokenizer.so | Yes | PHP token parsing | +| **Phar** | phar.so | Yes | PHP Archive support | +| **Ctype** | ctype.so | Yes | Character type checking | + +### Database (13 extensions) + +Database connectivity and manipulation. + +| Extension | File | Default | Dependencies | Description | +|-----------|------|---------|--------------|-------------| +| **PDO** | pdo.so | Yes | - | PHP Data Objects base | +| **PDO MySQL** | pdo_mysql.so | Yes | pdo, mysqlnd | MySQL via PDO | +| **MySQLi** | mysqli.so | Yes | mysqlnd | MySQL improved | +| **MySQLnd** | mysqlnd.so | Yes | - | MySQL native driver | +| **PDO SQLite** | pdo_sqlite.so | Yes | pdo | SQLite via PDO | +| **SQLite3** | sqlite3.so | Yes | - | SQLite3 support | +| **PDO ODBC** | pdo_odbc.so | No | pdo | ODBC via PDO | +| **ODBC** | odbc.so | No | - | ODBC support | +| **LDAP** | ldap.so | No | - | LDAP directory access | +| **DBA** | dba.so | No | - | Database abstraction | +| **Redis** | redis.so | No | igbinary | Redis client | +| **Memcached** | memcached.so | No | igbinary, msgpack | Memcached client | +| **MongoDB** | mongodb.so | No | - | MongoDB driver | + +### Cache (3 extensions) + +Caching and serialization for improved performance. + +| Extension | File | Default | Dependencies | Description | +|-----------|------|---------|--------------|-------------| +| **APCu** | apcu.so | No | - | User-land data cache | +| **Igbinary** | igbinary.so | No | - | Binary serialization | +| **MsgPack** | msgpack.so | No | session | MessagePack serialization | + +### Network (7 extensions) + +Network protocols and connectivity. + +| Extension | File | Default | Description | +|-----------|------|---------|-------------| +| **cURL** | curl.so | Yes | URL transfer library | +| **OpenSSL** | openssl.so | Yes | SSL/TLS cryptography | +| **Sockets** | sockets.so | Yes | Low-level socket interface | +| **FTP** | ftp.so | No | FTP protocol support | +| **SSH2** | ssh2.so | No | SSH2 protocol support | +| **SOAP** | soap.so | No | SOAP web services | +| **SNMP** | snmp.so | No | SNMP protocol support | + +### Text (4 extensions) + +Encoding, internationalization and text processing. + +| Extension | File | Default | Description | +|-----------|------|---------|-------------| +| **Mbstring** | mbstring.so | Yes | Multibyte string handling | +| **Iconv** | iconv.so | Yes | Character set conversion | +| **Intl** | intl.so | Yes | Internationalization (ICU) | +| **Gettext** | gettext.so | No | GNU gettext localization | + +### XML (6 extensions) + +XML parsing and manipulation. + +| Extension | File | Default | Dependencies | Description | +|-----------|------|---------|--------------|-------------| +| **XML** | xml.so | Yes | - | XML parser | +| **DOM** | dom.so | Yes | - | Document Object Model | +| **SimpleXML** | simplexml.so | Yes | - | Simple XML parsing | +| **XMLReader** | xmlreader.so | Yes | - | XML stream reader | +| **XMLWriter** | xmlwriter.so | Yes | - | XML stream writer | +| **XSL** | xsl.so | No | dom | XSL transformations | + +### Compression (6 extensions) + +Data compression algorithms. + +| Extension | File | Default | Description | +|-----------|------|---------|-------------| +| **Zip** | zip.so | Yes | ZIP archive support | +| **Zlib** | zlib.so | Yes | Gzip compression | +| **Bzip2** | bz2.so | No | Bzip2 compression | +| **Zstd** | zstd.so | No | Zstandard compression | +| **Brotli** | brotli.so | No | Brotli compression | +| **LZF** | lzf.so | No | LZF compression | + +### Image (2 extensions) + +Image processing and manipulation. + +| Extension | File | Default | Description | +|-----------|------|---------|-------------| +| **GD** | gd.so | Yes | Image creation and manipulation | +| **EXIF** | exif.so | Yes | EXIF metadata reading | + +### Crypto (4 extensions) + +Cryptography and security. + +| Extension | File | Default | Description | +|-----------|------|---------|-------------| +| **Sodium** | sodium.so | Yes | Modern cryptography (libsodium) | +| **GnuPG** | gnupg.so | No | GPG encryption/signing | +| **Scrypt** | scrypt.so | No | Scrypt key derivation | +| **Base58** | base58.so | No | Base58 encoding | + +### Math (3 extensions) + +Mathematical operations. + +| Extension | File | Default | Description | +|-----------|------|---------|-------------| +| **BCMath** | bcmath.so | Yes | Arbitrary precision math | +| **GMP** | gmp.so | No | GNU Multiple Precision | +| **FFI** | ffi.so | No | Foreign Function Interface | + +### System (9 extensions) + +System-level interaction. + +| Extension | File | Default | Description | +|-----------|------|---------|-------------| +| **PCNTL** | pcntl.so | No | Process control | +| **POSIX** | posix.so | No | POSIX functions | +| **Readline** | readline.so | No | Interactive CLI | +| **Shmop** | shmop.so | No | Shared memory | +| **SysV Msg** | sysvmsg.so | No | System V messages | +| **SysV Sem** | sysvsem.so | No | System V semaphores | +| **SysV Shm** | sysvshm.so | No | System V shared memory | +| **Inotify** | inotify.so | No | File system monitoring | +| **UUID** | uuid.so | No | UUID generation | + +### Debug (6 extensions) + +Development and debugging tools. + +| Extension | File | Default | Type | Description | +|-----------|------|---------|------|-------------| +| **Xdebug** | xdebug.so | No | Zend | Step debugger and profiler | +| **PCOV** | pcov.so | No | - | Code coverage driver | +| **AST** | ast.so | No | - | Abstract Syntax Tree | +| **VLD** | vld.so | No | - | Opcode dumper | +| **XHProf** | xhprof.so | No | - | Hierarchical profiler | +| **Excimer** | excimer.so | No | - | Interrupting timer | + +### Data (9 extensions) + +Data format handling. + +| Extension | File | Default | Description | +|-----------|------|---------|-------------| +| **YAML** | yaml.so | No | YAML parsing | +| **SimdJSON** | simdjson.so | No | Fast JSON parsing | +| **Protobuf** | protobuf.so | No | Protocol Buffers | +| **DS** | ds.so | No | Data Structures | +| **CSV** | csv.so | No | CSV handling | +| **JSON Post** | json_post.so | No | JSON POST handling | +| **JSONPath** | jsonpath.so | No | JSONPath queries | +| **Var Rep** | var_representation.so | No | Variable representation | +| **PSR** | psr.so | No | PSR interfaces | + +### Async (3 extensions) + +Asynchronous and event-driven programming. + +| Extension | File | Default | Dependencies | Description | +|-----------|------|---------|--------------|-------------| +| **Swoole** | swoole.so | No | - | Coroutine-based framework | +| **Ev** | ev.so | No | sockets | libev event loop | +| **Event** | event.so | No | sockets | libevent wrapper | + +### Misc (18 extensions) + +Miscellaneous extensions. + +| Extension | File | Default | Dependencies | Description | +|-----------|------|---------|--------------|-------------| +| **Tidy** | tidy.so | No | - | HTML/XHTML cleanup | +| **Calendar** | calendar.so | No | - | Calendar conversions | +| **Timezonedb** | timezonedb.so | No | - | Timezone database | +| **MaxMindDB** | maxminddb.so | No | - | GeoIP2 database reader | +| **Mailparse** | mailparse.so | No | mbstring | Email parsing | +| **OAuth** | oauth.so | No | - | OAuth authentication | +| **AMQP** | amqp.so | No | - | RabbitMQ client | +| **APFD** | apfd.so | No | - | Form data parsing | +| **Bitset** | bitset.so | No | - | Bit manipulation | +| **Geospatial** | geospatial.so | No | - | Geospatial functions | +| **Parle** | parle.so | No | - | Lexer/parser | +| **Quickhash** | quickhash.so | No | - | Fast hash functions | +| **Sync** | sync.so | No | - | Synchronization primitives | +| **Trader** | trader.so | No | - | Technical analysis | +| **Translit** | translit.so | No | - | Transliteration | +| **UploadProgress** | uploadprogress.so | No | - | Upload progress tracking | +| **Xattr** | xattr.so | No | - | Extended attributes | +| **Xlswriter** | xlswriter.so | No | - | Excel writer | + +--- + +## Extension Dependencies + +Some extensions require other extensions to be enabled first. + +| Extension | Required Dependencies | +|-----------|----------------------| +| PDO MySQL | pdo, mysqlnd | +| MySQLi | mysqlnd | +| PDO SQLite | pdo | +| PDO ODBC | pdo | +| Redis | igbinary | +| Memcached | igbinary, msgpack | +| MsgPack | session | +| XSL | dom | +| Ev | sockets | +| Event | sockets | +| Mailparse | mbstring | + +--- + +## Installation Profiles + +During installation, you can select from predefined profiles: + +| Profile | Description | Extensions | +|---------|-------------|------------| +| **Minimal** | Basic PHP | Core only | +| **Standard** | Web applications | Core + Database + Network + XML | +| **Full** | All extensions | All 100 extensions | +| **Custom** | Choose your own | Manual selection | + +--- + +## Configuration Files + +Extensions are configured via individual `.ini` files in: +``` +/var/packages/php84/etc/conf.d/ +``` + +File naming convention: +- `00-core.ini` - Core extensions (loaded first) +- `10-database.ini` - Database extensions +- `20-cache.ini` - Cache extensions +- etc. + +To manually enable/disable an extension: +```bash +# Disable an extension +mv /var/packages/php84/etc/conf.d/20-redis.ini /var/packages/php84/etc/conf.d/20-redis.ini.disabled + +# Enable an extension +mv /var/packages/php84/etc/conf.d/20-redis.ini.disabled /var/packages/php84/etc/conf.d/20-redis.ini + +# Restart PHP-FPM +synopkg restart php84 +``` + +--- + +## Verifying Extensions + +Check loaded extensions: +```bash +/var/packages/php84/target/bin/php -m +``` + +Check specific extension: +```bash +/var/packages/php84/target/bin/php -m | grep redis +``` + +Check extension configuration: +```bash +/var/packages/php84/target/bin/php -i | grep -A 20 "redis" +``` + +--- + +## Troubleshooting + +### Extension not loading + +1. Check if the `.so` file exists: + ```bash + ls -la /var/packages/php84/target/lib/php/modules/ + ``` + +2. Check for missing dependencies: + ```bash + ldd /var/packages/php84/target/lib/php/modules/redis.so + ``` + +3. Check PHP error log: + ```bash + tail -f /var/packages/php84/var/log/php-fpm.log + ``` + +### Dependency errors + +If an extension fails to load due to missing dependencies: +1. Enable the required extension first +2. Restart PHP-FPM +3. Then enable the dependent extension + +--- + +## Support + +- **Package**: PHP 8.4.15 for Synology DSM 7.2+ +- **Architecture**: geminilake (DS920+, DS720+, etc.) +- **Repository**: https://github.com/[your-repo]/php84 diff --git a/spk/php84/Makefile b/spk/php84/Makefile new file mode 100644 index 00000000000..4669f28e7b1 --- /dev/null +++ b/spk/php84/Makefile @@ -0,0 +1,65 @@ +# PHP 8.4.15 SPK Package for Synology NAS +# spksrc-compliant Makefile + +SPK_NAME = php84 +SPK_VERS = 8.4.15 +SPK_REV = 1 +SPK_ICON = src/php84.png + +# Cross-compilation dependency +DEPENDS = cross/php84 + +# Exclude non-x86_64 architectures +UNSUPPORTED_ARCHS = $(PPC_ARCHS) $(ARM_ARCHS) $(ARMv7_ARCHS) $(ARMv7L_ARCHS) $(ARMv8_ARCHS) $(i686_ARCHS) + +# DSM 7.2+ required +REQUIRED_MIN_DSM = 7.2 + +# Package metadata +MAINTAINER = SynoCommunity +DESCRIPTION = PHP 8.4.15 Hypertext Preprocessor with Extension Manager. Includes 163 extensions organized by theme with a graphical management interface. +DESCRIPTION_FRE = PHP 8.4.15 avec gestionnaire d extensions. Inclut 163 extensions organisees par theme avec une interface de gestion graphique. +DISPLAY_NAME = PHP 8.4 +CHANGELOG = Initial spksrc release with Extension Manager +HOMEPAGE = https://www.php.net +LICENSE = PHP-3.01 + +# Service configuration - PHP-FPM daemon +STARTABLE = yes +SERVICE_SETUP = src/service-setup.sh +SERVICE_USER = auto +SERVICE_PORT = 9000 +SERVICE_PORT_TITLE = PHP-FPM + +# Commands to link in /usr/local/bin +SPK_COMMANDS = bin/php bin/phpize bin/php-config bin/pear bin/pecl sbin/php-fpm + +# DSM UI integration - ExtJS application (not URL shortcut) +DSM_UI_DIR = app +DSM_UI_CONFIG = src/app/config +DSM_APP_NAME = SYNO.SDS.PHP84Manager.Application + +# Wizard for installation +WIZARDS_DIR = src/wizard7/ + +# Additional configuration files +CONF_DIR = src/conf/ + +# Post-strip target to install Extension Manager UI +POST_STRIP_TARGET = php84_install_ui + +include ../../mk/spksrc.common.mk +include ../../mk/spksrc.spk.mk + +.PHONY: php84_install_ui +php84_install_ui: + @$(MSG) "Installing Extension Manager UI" + install -m 755 -d $(STAGING_DIR)/app + install -m 755 -d $(STAGING_DIR)/app/images + install -m 755 -d $(STAGING_DIR)/app/cgi + install -m 644 src/app/config $(STAGING_DIR)/app/ + install -m 644 src/app/PHP84Manager.js $(STAGING_DIR)/app/ + install -m 644 src/app/style.css $(STAGING_DIR)/app/ + cp -a src/app/images/* $(STAGING_DIR)/app/images/ + install -m 755 src/app/index.cgi $(STAGING_DIR)/app/ + install -m 755 src/app/cgi/*.cgi $(STAGING_DIR)/app/cgi/ diff --git a/spk/php84/src/app/PHP84Manager.js b/spk/php84/src/app/PHP84Manager.js new file mode 100644 index 00000000000..df3b63f1577 --- /dev/null +++ b/spk/php84/src/app/PHP84Manager.js @@ -0,0 +1,45 @@ +/** + * PHP 8.4 Extension Manager for Synology DSM 7 + */ + +Ext.ns('SYNO.SDS.PHP84Manager'); + +// Utils (dependency) +SYNO.SDS.PHP84Manager.Utils = {}; + +// Main Window +SYNO.SDS.PHP84Manager.MainWindow = Ext.extend(SYNO.SDS.AppWindow, { + constructor: function(config) { + var self = this; + self.appInstance = config.appInstance; + + SYNO.SDS.PHP84Manager.MainWindow.superclass.constructor.call(self, Ext.apply({ + title: 'PHP 8.4 Extension Manager', + width: 950, + height: 650, + minWidth: 800, + minHeight: 500, + resizable: true, + maximizable: true, + minimizable: true, + layout: 'fit', + items: [{ + xtype: 'box', + autoEl: { + tag: 'iframe', + src: 'webman/3rdparty/php84/index.cgi?t=' + new Date().getTime(), + style: 'width:100%;height:100%;border:none;' + } + }] + }, config)); + } +}); + +// Application +SYNO.SDS.PHP84Manager.Application = Ext.extend(SYNO.SDS.AppInstance, { + appWindowName: 'SYNO.SDS.PHP84Manager.MainWindow', + + constructor: function(config) { + SYNO.SDS.PHP84Manager.Application.superclass.constructor.call(this, config); + } +}); diff --git a/spk/php84/src/app/cgi/config.cgi b/spk/php84/src/app/cgi/config.cgi new file mode 100755 index 00000000000..07dd294a9a3 --- /dev/null +++ b/spk/php84/src/app/cgi/config.cgi @@ -0,0 +1,146 @@ +#!/bin/sh +# PHP 8.4 Configuration CGI Script +# POSIX-compliant - Handles GET (read) and POST (update) for PHP configuration + +# Package paths +SYNOPKG_PKGDEST="${SYNOPKG_PKGDEST:-/var/packages/php84/target}" +SYNOPKG_PKGVAR="${SYNOPKG_PKGVAR:-/var/packages/php84/var}" + +# Paths +PHP_BIN="${SYNOPKG_PKGDEST}/bin/php" +PHP_INI="${SYNOPKG_PKGVAR}/etc/php.ini" +USER_CONFIG="${SYNOPKG_PKGVAR}/config.json" + +# Output headers +echo "Content-Type: application/json" +echo "" + +# Check PHP binary +if [ ! -x "$PHP_BIN" ]; then + echo '{"success":false,"error":"PHP binary not found"}' + exit 1 +fi + +# Process with PHP +"$PHP_BIN" -r ' + "256M", + "max_execution_time" => 60, + "upload_max_filesize" => "64M", + "post_max_size" => "64M", + "timezone" => "UTC", + "display_errors" => false, + "error_reporting" => "E_ALL & ~E_DEPRECATED & ~E_STRICT" + ]; + + // Read from user config if exists + if (file_exists($userConfig)) { + $userData = json_decode(file_get_contents($userConfig), true); + if ($userData && isset($userData["settings"])) { + $config = array_merge($config, $userData["settings"]); + } + } + + // Read actual values from php.ini if exists + if (file_exists($phpIni)) { + $iniContent = file_get_contents($phpIni); + + // Parse key values + if (preg_match("/^memory_limit\s*=\s*(.+)$/m", $iniContent, $m)) { + $config["memory_limit"] = trim($m[1]); + } + if (preg_match("/^max_execution_time\s*=\s*(\d+)/m", $iniContent, $m)) { + $config["max_execution_time"] = (int)$m[1]; + } + if (preg_match("/^upload_max_filesize\s*=\s*(.+)$/m", $iniContent, $m)) { + $config["upload_max_filesize"] = trim($m[1]); + } + if (preg_match("/^post_max_size\s*=\s*(.+)$/m", $iniContent, $m)) { + $config["post_max_size"] = trim($m[1]); + } + if (preg_match("/^date\.timezone\s*=\s*[\"'\'']*([^\"'\''\n]+)/m", $iniContent, $m)) { + $config["timezone"] = trim($m[1], "\"'\''"); + } + if (preg_match("/^display_errors\s*=\s*(On|Off|1|0)/mi", $iniContent, $m)) { + $config["display_errors"] = in_array(strtolower($m[1]), ["on", "1"]); + } + } + + return $config; +} + +// Update configuration +function updateConfig($phpIni, $userConfig, $newSettings) { + // Read current php.ini + $iniContent = file_exists($phpIni) ? file_get_contents($phpIni) : ""; + + // Update values + $updates = [ + "memory_limit" => $newSettings["memory_limit"] ?? null, + "max_execution_time" => $newSettings["max_execution_time"] ?? null, + "upload_max_filesize" => $newSettings["upload_max_filesize"] ?? null, + "post_max_size" => $newSettings["post_max_size"] ?? null, + "date.timezone" => isset($newSettings["timezone"]) ? "\"{$newSettings["timezone"]}\"" : null, + "display_errors" => isset($newSettings["display_errors"]) ? ($newSettings["display_errors"] ? "On" : "Off") : null + ]; + + foreach ($updates as $key => $value) { + if ($value === null) continue; + + $pattern = "/^" . preg_quote($key, "/") . "\s*=.*$/m"; + $replacement = "$key = $value"; + + if (preg_match($pattern, $iniContent)) { + $iniContent = preg_replace($pattern, $replacement, $iniContent); + } else { + $iniContent .= "\n$replacement"; + } + } + + // Write php.ini + file_put_contents($phpIni, $iniContent); + + // Update user config + $userData = file_exists($userConfig) ? json_decode(file_get_contents($userConfig), true) : []; + $userData["settings"] = $newSettings; + $userData["last_modified"] = date("c"); + file_put_contents($userConfig, json_encode($userData, JSON_PRETTY_PRINT)); + + return true; +} + +// Handle GET +if ($method === "GET") { + $config = readConfig($phpIni, $userConfig); + echo json_encode(["success" => true, "config" => $config]); + exit(0); +} + +// Handle POST +if ($method === "POST") { + $input = file_get_contents("php://input"); + $data = json_decode($input, true); + + if (!$data || !isset($data["settings"])) { + echo json_encode(["success" => false, "error" => "Invalid request data"]); + exit(1); + } + + if (updateConfig($phpIni, $userConfig, $data["settings"])) { + echo json_encode(["success" => true, "message" => "Configuration updated"]); + } else { + echo json_encode(["success" => false, "error" => "Failed to update configuration"]); + } + exit(0); +} + +echo json_encode(["success" => false, "error" => "Method not allowed"]); +?> +' diff --git a/spk/php84/src/app/cgi/extensions.cgi b/spk/php84/src/app/cgi/extensions.cgi new file mode 100755 index 00000000000..29bd0ff2866 --- /dev/null +++ b/spk/php84/src/app/cgi/extensions.cgi @@ -0,0 +1,240 @@ +#!/bin/sh +# PHP 8.4 Extensions CGI Script +# POSIX-compliant - Uses PHP for JSON processing + +# Package paths +SYNOPKG_PKGDEST="${SYNOPKG_PKGDEST:-/var/packages/php84/target}" +SYNOPKG_PKGVAR="${SYNOPKG_PKGVAR:-/var/packages/php84/var}" + +# Paths +PHP_BIN="${SYNOPKG_PKGDEST}/bin/php" +EXTENSIONS_JSON="${SYNOPKG_PKGDEST}/conf/extensions.json" +CONF_D_DIR="${SYNOPKG_PKGVAR}/etc/conf.d" +EXT_DIR="${SYNOPKG_PKGDEST}/lib/php/extensions/no-debug-non-zts-20240924" + +# Output headers +echo "Content-Type: application/json" +echo "" + +# Check PHP binary +if [ ! -x "$PHP_BIN" ]; then + echo '{"success":false,"error":"PHP binary not found"}' + exit 1 +fi + +# Create PHP script for JSON processing +process_request() { + "$PHP_BIN" -r ' + false, "error" => "Extensions configuration not found"]); + exit(1); +} + +// Get request method and data +$method = $_SERVER["REQUEST_METHOD"] ?? "GET"; +$queryString = $_SERVER["QUERY_STRING"] ?? ""; +parse_str($queryString, $query); +$action = $query["action"] ?? ""; + +// Check if extension .so file exists +function extensionAvailable($extId, $extDir) { + return file_exists("$extDir/$extId.so"); +} + +// Get load order prefix for extension (must match postinst) +function getExtensionPrefix($extId) { + switch ($extId) { + // Core extensions that others depend on - load first (00-09) + case "mysqlnd": return "00"; + case "pdo": return "01"; + case "igbinary": return "02"; + case "msgpack": return "03"; + // Database extensions depending on pdo/mysqlnd (10-19) + case "pdo_mysql": + case "pdo_sqlite": + case "pdo_pgsql": return "10"; + case "mysqli": return "11"; + case "sqlite3": return "12"; + // Extensions depending on igbinary/msgpack (20-29) + case "redis": return "20"; + case "memcached": return "21"; + // All other extensions (50+) + default: return "50"; + } +} + +// Get ini filename with prefix +function getIniFilename($extId) { + return getExtensionPrefix($extId) . "-" . $extId . ".ini"; +} + +// Check if extension is enabled (has .ini file with or without prefix) +function extensionEnabled($extId, $confDir) { + // Check prefixed format first (new format) + $prefixedFile = $confDir . "/" . getIniFilename($extId); + if (file_exists($prefixedFile)) return true; + // Check legacy non-prefixed format + if (file_exists("$confDir/$extId.ini")) return true; + return false; +} + +// Get all ini files for an extension (for cleanup) +function getExtensionIniFiles($extId, $confDir) { + $files = []; + // Prefixed format + $prefixedFile = $confDir . "/" . getIniFilename($extId); + if (file_exists($prefixedFile)) $files[] = $prefixedFile; + // Legacy non-prefixed format + $legacyFile = "$confDir/$extId.ini"; + if (file_exists($legacyFile)) $files[] = $legacyFile; + // Also check for any other prefixed variants (XX-extId.ini) + $pattern = $confDir . "/*-" . $extId . ".ini"; + foreach (glob($pattern) as $f) { + if (!in_array($f, $files)) $files[] = $f; + } + return $files; +} + +// GET: List extensions +if ($method === "GET") { + if ($action === "categories") { + // Return categories + $categories = []; + foreach ($config["categories"] as $id => $cat) { + $categories[] = [ + "id" => $id, + "name" => $cat["name"], + "description" => $cat["description"] ?? "", + "order" => $cat["order"] ?? 99 + ]; + } + usort($categories, fn($a, $b) => $a["order"] <=> $b["order"]); + echo json_encode(["success" => true, "categories" => $categories]); + } else { + // Return extensions with their status + $extensions = []; + foreach ($config["extensions"] as $id => $ext) { + $category = $ext["category"] ?? "misc"; + $catInfo = $config["categories"][$category] ?? ["name" => "Autres"]; + + $extensions[] = [ + "id" => $id, + "name" => $ext["name"], + "category" => $category, + "categoryName" => $catInfo["name"], + "filename" => $ext["filename"] ?? "$id.so", + "default" => $ext["default"] ?? false, + "dependencies" => $ext["dependencies"] ?? [], + "zend_extension" => $ext["zend_extension"] ?? false, + "available" => extensionAvailable($id, $extDir), + "enabled" => extensionEnabled($id, $confDir) + ]; + } + echo json_encode(["success" => true, "extensions" => $extensions, "total" => count($extensions)]); + } + exit(0); +} + +// POST: Update extensions +if ($method === "POST") { + $input = file_get_contents("php://input"); + $data = json_decode($input, true); + + if (!$data || !isset($data["extensions"])) { + echo json_encode(["success" => false, "error" => "Invalid request data"]); + exit(1); + } + + // Ensure conf.d directory exists + if (!is_dir($confDir)) { + mkdir($confDir, 0755, true); + } + + $updated = 0; + $errors = []; + + foreach ($data["extensions"] as $extId => $enable) { + // Use prefixed filename (matches postinst format) + $iniFile = "$confDir/" . getIniFilename($extId); + $soFile = "$extDir/$extId.so"; + + if ($enable) { + // Enable extension + if (!file_exists($soFile)) { + $errors[] = "Extension $extId not available"; + continue; + } + + // First, remove any existing ini files (cleanup duplicates) + foreach (getExtensionIniFiles($extId, $confDir) as $oldFile) { + @unlink($oldFile); + } + + // Determine if Zend extension + $extInfo = $config["extensions"][$extId] ?? []; + $isZend = in_array($extId, ["opcache", "xdebug"]) || ($extInfo["zend_extension"] ?? false); + + $content = $isZend ? "zend_extension=$extId.so\n" : "extension=$extId.so\n"; + + // Add extension-specific config + if ($extId === "opcache") { + $content .= "opcache.enable=1\n"; + $content .= "opcache.enable_cli=0\n"; + $content .= "opcache.memory_consumption=128\n"; + $content .= "opcache.interned_strings_buffer=8\n"; + $content .= "opcache.max_accelerated_files=10000\n"; + } elseif ($extId === "apcu") { + $content .= "apc.enabled=1\n"; + $content .= "apc.shm_size=32M\n"; + $content .= "apc.ttl=7200\n"; + } elseif ($extId === "xdebug") { + $content .= "xdebug.mode=off\n"; + $content .= "xdebug.start_with_request=no\n"; + } + + file_put_contents($iniFile, $content); + chmod($iniFile, 0644); + $updated++; + } else { + // Disable extension - remove ALL ini files (prefixed and legacy) + $removed = false; + foreach (getExtensionIniFiles($extId, $confDir) as $oldFile) { + @unlink($oldFile); + $removed = true; + } + if ($removed) $updated++; + } + } + + $response = ["success" => true, "updated" => $updated]; + if (!empty($errors)) { + $response["errors"] = $errors; + } + echo json_encode($response); + exit(0); +} + +echo json_encode(["success" => false, "error" => "Method not allowed"]); +?> +' +} + +# Export environment variables for PHP +export EXT_DIR +export CONF_D_DIR +export EXTENSIONS_JSON +export REQUEST_METHOD +export QUERY_STRING +export CONTENT_LENGTH + +# Run PHP processor +process_request diff --git a/spk/php84/src/app/cgi/service.cgi b/spk/php84/src/app/cgi/service.cgi new file mode 100755 index 00000000000..8ad1d67eeaf --- /dev/null +++ b/spk/php84/src/app/cgi/service.cgi @@ -0,0 +1,138 @@ +#!/bin/sh +# PHP 8.4 Service Control CGI Script +# POSIX-compliant - Handles service start/stop/restart operations + +# Package paths +SYNOPKG_PKGDEST="${SYNOPKG_PKGDEST:-/var/packages/php84/target}" +SYNOPKG_PKGVAR="${SYNOPKG_PKGVAR:-/var/packages/php84/var}" +SYNOPKG_PKGNAME="${SYNOPKG_PKGNAME:-php84}" + +# Paths +SERVICE_SCRIPT="${SYNOPKG_PKGDEST}/scripts/start-stop-status" +PID_FILE="${SYNOPKG_PKGVAR}/run/php-fpm.pid" + +# Output headers +echo "Content-Type: application/json" +echo "" + +# Check if service is running +is_running() { + if [ -f "$PID_FILE" ]; then + pid=$(cat "$PID_FILE" 2>/dev/null) + if [ -n "$pid" ] && kill -0 "$pid" 2>/dev/null; then + return 0 + fi + fi + return 1 +} + +# Get service status +get_status() { + if is_running; then + pid=$(cat "$PID_FILE" 2>/dev/null) + echo "{\"success\":true,\"status\":\"running\",\"pid\":$pid}" + else + echo '{"success":true,"status":"stopped","pid":null}' + fi +} + +# Parse action from query string +get_action() { + echo "$QUERY_STRING" | tr '&' '\n' | while read param; do + key=$(echo "$param" | cut -d'=' -f1) + if [ "$key" = "action" ]; then + echo "$param" | cut -d'=' -f2 + return + fi + done +} + +# Main request handler +REQUEST_METHOD="${REQUEST_METHOD:-GET}" +action=$(get_action) + +case "$REQUEST_METHOD" in + GET) + case "$action" in + status|"") + get_status + ;; + *) + echo '{"success":false,"error":"Unknown action"}' + ;; + esac + ;; + POST) + # Read POST data to get action + if [ -n "$CONTENT_LENGTH" ] && [ "$CONTENT_LENGTH" -gt 0 ]; then + read -r POST_DATA + # Extract action from JSON (simple parsing) + action=$(echo "$POST_DATA" | tr ',' '\n' | tr -d '{}\"' | while read line; do + key=$(echo "$line" | cut -d':' -f1 | tr -d ' ') + if [ "$key" = "action" ]; then + echo "$line" | cut -d':' -f2 | tr -d ' ' + break + fi + done) + fi + + case "$action" in + start) + if is_running; then + echo '{"success":true,"message":"Service already running"}' + else + # Use synopkg to start service + synopkg start "$SYNOPKG_PKGNAME" >/dev/null 2>&1 + sleep 2 + if is_running; then + echo '{"success":true,"message":"Service started"}' + else + echo '{"success":false,"error":"Failed to start service"}' + fi + fi + ;; + stop) + if ! is_running; then + echo '{"success":true,"message":"Service not running"}' + else + synopkg stop "$SYNOPKG_PKGNAME" >/dev/null 2>&1 + sleep 2 + if ! is_running; then + echo '{"success":true,"message":"Service stopped"}' + else + echo '{"success":false,"error":"Failed to stop service"}' + fi + fi + ;; + restart) + synopkg restart "$SYNOPKG_PKGNAME" >/dev/null 2>&1 + sleep 3 + if is_running; then + echo '{"success":true,"message":"Service restarted"}' + else + echo '{"success":false,"error":"Failed to restart service"}' + fi + ;; + reload) + if ! is_running; then + echo '{"success":false,"error":"Service not running"}' + else + pid=$(cat "$PID_FILE" 2>/dev/null) + kill -USR2 "$pid" 2>/dev/null + sleep 1 + if is_running; then + echo '{"success":true,"message":"Configuration reloaded"}' + else + echo '{"success":false,"error":"Service crashed during reload"}' + fi + fi + ;; + *) + echo '{"success":false,"error":"Unknown action. Use: start, stop, restart, reload"}' + ;; + esac + ;; + *) + echo '{"success":false,"error":"Method not allowed"}' + ;; +esac diff --git a/spk/php84/src/app/cgi/validate.cgi b/spk/php84/src/app/cgi/validate.cgi new file mode 100755 index 00000000000..913a541b7cc --- /dev/null +++ b/spk/php84/src/app/cgi/validate.cgi @@ -0,0 +1,220 @@ +#!/bin/sh +# PHP 8.4 Validation CGI Script +# POSIX-compliant - Validates extension dependencies and configuration + +# Package paths +SYNOPKG_PKGDEST="${SYNOPKG_PKGDEST:-/var/packages/php84/target}" +SYNOPKG_PKGVAR="${SYNOPKG_PKGVAR:-/var/packages/php84/var}" + +# Paths +PHP_BIN="${SYNOPKG_PKGDEST}/bin/php" +EXTENSIONS_JSON="${SYNOPKG_PKGDEST}/conf/extensions.json" +CONF_D_DIR="${SYNOPKG_PKGVAR}/etc/conf.d" +EXT_DIR="${SYNOPKG_PKGDEST}/lib/php/extensions/no-debug-non-zts-20240924" + +# Output headers +echo "Content-Type: application/json" +echo "" + +# Check PHP binary +if [ ! -x "$PHP_BIN" ]; then + echo '{"success":false,"error":"PHP binary not found"}' + exit 1 +fi + +# Export environment variables for PHP +export EXTENSIONS_JSON +export CONF_D_DIR +export EXT_DIR + +# Process with PHP +"$PHP_BIN" -r ' + false, "error" => "Extensions configuration not found"]); + exit(1); +} + +// Check if extension is enabled +function isEnabled($extId, $confDir) { + return file_exists("$confDir/$extId.ini"); +} + +// Check if extension is available +function isAvailable($extId, $extDir) { + return file_exists("$extDir/$extId.so"); +} + +// Validate dependencies for an extension +function validateDependencies($extId, $config, $confDir, $extDir) { + $ext = $config["extensions"][$extId] ?? null; + if (!$ext) { + return ["valid" => false, "error" => "Unknown extension: $extId"]; + } + + $deps = $ext["dependencies"] ?? []; + $missing = []; + + foreach ($deps as $dep) { + if (!isEnabled($dep, $confDir) && !isAvailable($dep, $extDir)) { + $missing[] = $dep; + } + } + + if (!empty($missing)) { + return [ + "valid" => false, + "extension" => $extId, + "missing_dependencies" => $missing, + "message" => "Extension $extId requires: " . implode(", ", $missing) + ]; + } + + return ["valid" => true, "extension" => $extId]; +} + +// Validate all enabled extensions +function validateAll($config, $confDir, $extDir) { + $results = []; + $errors = []; + + foreach ($config["extensions"] as $extId => $ext) { + if (isEnabled($extId, $confDir)) { + $validation = validateDependencies($extId, $config, $confDir, $extDir); + if (!$validation["valid"]) { + $errors[] = $validation; + } + $results[] = $validation; + } + } + + return [ + "success" => true, + "valid" => empty($errors), + "errors" => $errors, + "checked" => count($results) + ]; +} + +// Calculate dependencies for enabling an extension +function getDependencyChain($extId, $config) { + $ext = $config["extensions"][$extId] ?? null; + if (!$ext) { + return []; + } + + $deps = $ext["dependencies"] ?? []; + $chain = $deps; + + // Recursively get dependencies of dependencies + foreach ($deps as $dep) { + $subDeps = getDependencyChain($dep, $config); + $chain = array_merge($chain, $subDeps); + } + + return array_unique($chain); +} + +// Calculate what extensions depend on this one +function getDependents($extId, $config) { + $dependents = []; + + foreach ($config["extensions"] as $id => $ext) { + $deps = $ext["dependencies"] ?? []; + if (in_array($extId, $deps)) { + $dependents[] = $id; + } + } + + return $dependents; +} + +// Handle request +$queryString = $_SERVER["QUERY_STRING"] ?? ""; +parse_str($queryString, $query); +$action = $query["action"] ?? "validate"; +$extension = $query["extension"] ?? null; + +switch ($action) { + case "validate": + if ($extension) { + // Validate specific extension + $result = validateDependencies($extension, $config, $confDir, $extDir); + $result["success"] = true; + echo json_encode($result); + } else { + // Validate all enabled extensions + echo json_encode(validateAll($config, $confDir, $extDir)); + } + break; + + case "dependencies": + if (!$extension) { + echo json_encode(["success" => false, "error" => "Extension parameter required"]); + break; + } + $chain = getDependencyChain($extension, $config); + echo json_encode([ + "success" => true, + "extension" => $extension, + "dependencies" => $chain, + "count" => count($chain) + ]); + break; + + case "dependents": + if (!$extension) { + echo json_encode(["success" => false, "error" => "Extension parameter required"]); + break; + } + $dependents = getDependents($extension, $config); + echo json_encode([ + "success" => true, + "extension" => $extension, + "dependents" => $dependents, + "count" => count($dependents) + ]); + break; + + case "check": + // Quick check if an extension can be enabled + if (!$extension) { + echo json_encode(["success" => false, "error" => "Extension parameter required"]); + break; + } + + $available = isAvailable($extension, $extDir); + $enabled = isEnabled($extension, $confDir); + $deps = getDependencyChain($extension, $config); + $missingDeps = []; + + foreach ($deps as $dep) { + if (!isEnabled($dep, $confDir)) { + $missingDeps[] = $dep; + } + } + + echo json_encode([ + "success" => true, + "extension" => $extension, + "available" => $available, + "enabled" => $enabled, + "can_enable" => $available && empty($missingDeps), + "missing_dependencies" => $missingDeps + ]); + break; + + default: + echo json_encode(["success" => false, "error" => "Unknown action"]); +} +?> +' diff --git a/spk/php84/src/app/config b/spk/php84/src/app/config new file mode 100644 index 00000000000..ccf8a4ae882 --- /dev/null +++ b/spk/php84/src/app/config @@ -0,0 +1,25 @@ +{ + "PHP84Manager.js": { + "SYNO.SDS.PHP84Manager.Application": { + "type": "app", + "title": "PHP 8.4 Extension Manager", + "appWindow": "SYNO.SDS.PHP84Manager.MainWindow", + "desc": "Gestionnaire d'extensions PHP 8.4", + "icon": "images/php84_{0}.png", + "allowMultiInstance": false, + "allowStandalone": true, + "allowSharing": false, + "allUsers": true, + "grantPrivilege": "all", + "advanceGrantPrivilege": true, + "depend": ["SYNO.SDS.PHP84Manager.MainWindow"] + }, + "SYNO.SDS.PHP84Manager.MainWindow": { + "type": "lib", + "title": "PHP 8.4 Extension Manager", + "icon": "images/php84_{0}.png", + "depend": ["SYNO.SDS.PHP84Manager.Utils"] + }, + "SYNO.SDS.PHP84Manager.Utils": [] + } +} diff --git a/spk/php84/src/app/images/icon_all.png b/spk/php84/src/app/images/icon_all.png new file mode 100644 index 00000000000..e5d4b9e6f13 Binary files /dev/null and b/spk/php84/src/app/images/icon_all.png differ diff --git a/spk/php84/src/app/images/icon_category.png b/spk/php84/src/app/images/icon_category.png new file mode 100644 index 00000000000..e5d4b9e6f13 Binary files /dev/null and b/spk/php84/src/app/images/icon_category.png differ diff --git a/spk/php84/src/app/index.cgi b/spk/php84/src/app/index.cgi new file mode 100755 index 00000000000..5dfd148280d --- /dev/null +++ b/spk/php84/src/app/index.cgi @@ -0,0 +1,670 @@ +#!/bin/bash +# PHP 8.4 Extension Manager - Interactive Bash CGI +# Handles GET (display) and POST (toggle extensions) + +PATH=/bin:/sbin:/usr/bin:/usr/sbin:/usr/syno/bin:/usr/syno/sbin + +PKG_NAME="php84" +PKG_BASE="/var/packages/${PKG_NAME}" +CONF_DIR="${PKG_BASE}/var/etc/conf.d" +EXT_DIR="${PKG_BASE}/target/lib/php/extensions/no-debug-non-zts-20240924" +FPM_SERVICE="${PKG_NAME}-fpm" + +# Zend extensions list (require zend_extension= instead of extension=) +ZEND_EXTS="opcache xdebug" + +# Extension load order priorities (lower = loaded first) +# Core extensions that others depend on (20-) +PRIORITY_CORE="session sockets mysqlnd pdo igbinary" +# Extensions that depend on core extensions (70-) +PRIORITY_DEPENDENT="ev event msgpack mysqli pdo_mysql redis memcached" + +# Authentication check - DSM 7 compatible +# CGI scripts run as 'http' user via synoscgi +# We check multiple methods to verify DSM session + +AUTH_OK="false" +AUTH_USER="" + +# Method 1: Check for DSM session cookie (most reliable for CGI) +# The 'id=' cookie is set when user logs into DSM +if [ -n "$HTTP_COOKIE" ]; then + if echo "$HTTP_COOKIE" | grep -qE '(^|;)[[:space:]]*id='; then + AUTH_OK="true" + fi +fi + +# Method 2: login.cgi fallback (returns JSON with success status) +# Note: Output includes HTTP headers, so we grep for success in JSON +if [ "$AUTH_OK" = "false" ]; then + syno_login=$(/usr/syno/synoman/webman/login.cgi 2>/dev/null) + # Handle both "success":true and "success" : true formats + if echo "$syno_login" | grep -q 'success.*true'; then + AUTH_OK="true" + fi +fi + +# Method 3: authenticate.cgi (may not work in all CGI contexts) +if [ "$AUTH_OK" = "false" ]; then + AUTH_USER=$(/usr/syno/synoman/webman/modules/authenticate.cgi 2>/dev/null) + if [ -n "$AUTH_USER" ]; then + AUTH_OK="true" + fi +fi + +if [ "$AUTH_OK" = "false" ]; then + echo "Content-type: text/html" + echo "" + echo "Access denied" + exit 1 +fi + +# Function to find ini file for extension (with or without prefix) +find_ini_file() { + local ext="$1" + # Check for prefixed files first (XX-ext.ini) + local prefixed=$(ls "${CONF_DIR}/"*"-${ext}.ini" 2>/dev/null | head -1) + if [ -n "$prefixed" ]; then + echo "$prefixed" + return 0 + fi + # Check for non-prefixed file + if [ -f "${CONF_DIR}/${ext}.ini" ]; then + echo "${CONF_DIR}/${ext}.ini" + return 0 + fi + return 1 +} + +# Function to check if extension is enabled +is_enabled() { + local ext="$1" + find_ini_file "$ext" >/dev/null 2>&1 +} + +# Function to check if it's a zend extension +is_zend_ext() { + local ext="$1" + for z in $ZEND_EXTS; do + [ "$z" = "$ext" ] && return 0 + done + return 1 +} + +# Function to get priority prefix for extension load order +get_priority_prefix() { + local ext="$1" + # Core extensions load first (20-) + for e in $PRIORITY_CORE; do + [ "$e" = "$ext" ] && echo "20" && return + done + # Dependent extensions load last (70-) + for e in $PRIORITY_DEPENDENT; do + [ "$e" = "$ext" ] && echo "70" && return + done + # Standard extensions (50-) + echo "50" +} + +# Function to enable extension +enable_ext() { + local ext="$1" + local prefix=$(get_priority_prefix "$ext") + mkdir -p "${CONF_DIR}" + # Remove any existing .ini files for this extension (old prefixes) + rm -f "${CONF_DIR}/"*"-${ext}.ini" "${CONF_DIR}/${ext}.ini" 2>/dev/null + # Create new file with correct prefix + if is_zend_ext "$ext"; then + echo "zend_extension=${ext}.so" > "${CONF_DIR}/${prefix}-${ext}.ini" + else + echo "extension=${ext}.so" > "${CONF_DIR}/${prefix}-${ext}.ini" + fi +} + +# Function to disable extension +disable_ext() { + local ext="$1" + # Remove both prefixed and non-prefixed files + rm -f "${CONF_DIR}/"*"-${ext}.ini" "${CONF_DIR}/${ext}.ini" 2>/dev/null +} + +# Handle POST request +if [ "$REQUEST_METHOD" = "POST" ]; then + # Read POST data + if [ -n "$CONTENT_LENGTH" ] && [ "$CONTENT_LENGTH" -gt 0 ]; then + read -n "$CONTENT_LENGTH" POST_DATA + else + POST_DATA="" + fi + + # Parse action and ext using sed (busybox compatible) + action=$(echo "$POST_DATA" | sed -n 's/.*action=\([^&]*\).*/\1/p') + ext=$(echo "$POST_DATA" | sed -n 's/.*ext=\([^&]*\).*/\1/p') + + # URL decode extension name (simple decode for common chars) + ext=$(echo "$ext" | sed 's/+/ /g; s/%2B/+/g; s/%20/ /g; s/%2F/\//g; s/%5F/_/g; s/%2D/-/g') + + echo "Content-type: application/json; charset=UTF-8" + echo "" + + if [ "$action" = "toggle" ] && [ -n "$ext" ]; then + # Validate extension exists + if [ -f "${EXT_DIR}/${ext}.so" ]; then + if is_enabled "$ext"; then + if disable_ext "$ext"; then + echo "{\"success\":true,\"enabled\":false,\"ext\":\"${ext}\"}" + else + echo "{\"success\":false,\"error\":\"Failed to disable extension\"}" + fi + else + if enable_ext "$ext"; then + echo "{\"success\":true,\"enabled\":true,\"ext\":\"${ext}\"}" + else + echo "{\"success\":false,\"error\":\"Failed to enable extension\"}" + fi + fi + else + echo "{\"success\":false,\"error\":\"Extension not found: ${ext}\"}" + fi + elif [ "$action" = "reload" ]; then + # Create a reload flag file that the watcher will detect + # The watcher (running as package user) will send USR2 to PHP-FPM + RELOAD_FLAG="${CONF_DIR}/reload.flag" + + if echo "reload_requested=$(date +%s)" > "$RELOAD_FLAG" 2>/dev/null; then + echo "{\"success\":true,\"message\":\"Rechargement demande\"}" + else + echo "{\"success\":false,\"error\":\"Cannot create reload flag\"}" + fi + else + echo "{\"success\":false,\"error\":\"Invalid action: ${action}\"}" + fi + exit 0 +fi + +# GET request - Display HTML interface +# Count extensions +total=0 +enabled=0 +for so in "${EXT_DIR}"/*.so; do + [ -f "$so" ] || continue + ((total++)) + name=$(basename "$so" .so) + if is_enabled "$name"; then + ((enabled++)) + fi +done + +echo "Content-type: text/html; charset=UTF-8" +echo "" + +cat << 'HTMLHEAD' + + + + + + PHP 8.4 Extension Manager + + + +
+
+

PHP 8.4 Extension Manager

+HTMLHEAD + +echo "

${enabled} / ${total} extensions activées

" + +cat << 'HTMLMID' +
+
+ + +
+
+ +
+ Des modifications ont été effectuées. Cliquez sur "Appliquer & Recharger" pour les activer. +
+ +
+ + + + + + +
+ +
+HTMLMID + +# List extensions +for so in "${EXT_DIR}"/*.so; do + [ -f "$so" ] || continue + name=$(basename "$so" .so) + + if is_enabled "$name"; then + echo "
" + echo " ${name}" + echo "
" + echo "
" + else + echo "
" + echo " ${name}" + echo "
" + echo "
" + fi +done + +cat << 'HTMLFOOT' +
+ + + + +HTMLFOOT + +exit 0 diff --git a/spk/php84/src/app/style.css b/spk/php84/src/app/style.css new file mode 100644 index 00000000000..65b02cc840b --- /dev/null +++ b/spk/php84/src/app/style.css @@ -0,0 +1,225 @@ +/** + * PHP 8.4 Extension Manager Styles + * DSM 7 Native Application + */ + +/* Main container */ +.php84-manager { + background: #f5f5f5; +} + +/* Category tree */ +.php84-category-tree { + background: #ffffff; + border-right: 1px solid #d0d0d0; +} + +.php84-category-tree .x-tree-node-el { + padding: 4px 8px; +} + +.php84-category-tree .x-tree-selected { + background: #e8f4fc; + border-radius: 3px; +} + +.php84-icon-all { + background-image: url(images/icon_all.png); +} + +.php84-icon-category { + background-image: url(images/icon_category.png); +} + +/* Extension grid */ +.php84-extension-grid { + background: #ffffff; +} + +.php84-extension-grid .x-grid3-header { + background: #f0f0f0; + border-bottom: 1px solid #d0d0d0; +} + +.php84-extension-grid .x-grid3-row { + border-bottom: 1px solid #eee; +} + +.php84-extension-grid .x-grid3-row:hover { + background: #f8f8f8; +} + +.php84-extension-grid .x-grid3-row-selected { + background: #e8f4fc !important; +} + +/* Row states */ +.php84-row-enabled { + background: #f0fff0; +} + +.php84-row-unavailable { + background: #f5f5f5; + color: #999; +} + +.php84-row-unavailable td { + color: #999 !important; +} + +/* Extension name */ +.php84-ext-name { + font-weight: 500; + color: #333; +} + +.php84-ext-name-disabled { + font-weight: 500; + color: #999; + font-style: italic; +} + +/* Status badges */ +.php84-status-enabled { + display: inline-block; + padding: 2px 8px; + background: #4caf50; + color: #fff; + border-radius: 3px; + font-size: 11px; + font-weight: 500; +} + +.php84-status-disabled { + display: inline-block; + padding: 2px 8px; + background: #9e9e9e; + color: #fff; + border-radius: 3px; + font-size: 11px; + font-weight: 500; +} + +.php84-status-unavailable { + display: inline-block; + padding: 2px 8px; + background: #e0e0e0; + color: #999; + border-radius: 3px; + font-size: 11px; +} + +/* Available badges */ +.php84-available-yes { + color: #4caf50; + font-weight: 500; +} + +.php84-available-no { + color: #f44336; + font-weight: 500; +} + +/* Apply button */ +.php84-apply-btn { + background: #2196f3 !important; + color: #fff !important; + border-radius: 3px; +} + +.php84-apply-btn:hover { + background: #1976d2 !important; +} + +/* Toolbar */ +.php84-manager .x-toolbar { + background: #fafafa; + border-bottom: 1px solid #e0e0e0; + padding: 5px 10px; +} + +/* Search field */ +#php84-search-field { + border-radius: 3px; +} + +/* Status bar */ +#php84-statusbar { + background: #fafafa; + border-top: 1px solid #e0e0e0; + padding: 5px 10px; +} + +/* Loading mask */ +.php84-loading { + background: rgba(255, 255, 255, 0.8); +} + +.php84-loading .x-mask-loading { + background-color: #fff; + border-radius: 5px; + padding: 10px 20px; + box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1); +} + +/* Category count badge */ +.php84-category-count { + display: inline-block; + padding: 1px 6px; + background: #e0e0e0; + color: #666; + border-radius: 10px; + font-size: 10px; + margin-left: 5px; +} + +/* Dependency warning */ +.php84-dep-warning { + background: #fff3e0; + border: 1px solid #ffcc80; + border-radius: 3px; + padding: 10px; + margin: 10px 0; +} + +.php84-dep-warning-icon { + color: #ff9800; + margin-right: 10px; +} + +/* Extension info tooltip */ +.php84-ext-tooltip { + max-width: 300px; +} + +.php84-ext-tooltip .deps { + margin-top: 5px; + padding-top: 5px; + border-top: 1px solid #eee; + font-size: 11px; + color: #666; +} + +/* Responsive adjustments */ +@media (max-width: 1024px) { + .php84-category-tree { + width: 150px; + } +} + +/* Icons placeholder - replace with actual icons */ +.syno-icon-refresh { + background-image: url(/webman/resources/images/default/tree/refresh.gif); +} + +.syno-icon-check { + background-image: url(/webman/resources/images/default/checkbox/checkbox-checked.gif); +} + +.syno-icon-uncheck { + background-image: url(/webman/resources/images/default/checkbox/checkbox.gif); +} + +.syno-icon-apply { + background-image: url(/webman/resources/images/default/shared/save.gif); +} diff --git a/spk/php84/src/conf/extensions.json b/spk/php84/src/conf/extensions.json new file mode 100644 index 00000000000..272b4fe7a8d --- /dev/null +++ b/spk/php84/src/conf/extensions.json @@ -0,0 +1,138 @@ +{ + "version": "2.0", + "php_version": "8.4.15", + "total_extensions": 100, + "categories": { + "core": {"name": "Core", "description": "Extensions essentielles", "order": 1, "wizard": true}, + "database": {"name": "Base de données", "description": "Connexion et manipulation BDD", "order": 2, "wizard": true}, + "cache": {"name": "Cache", "description": "Cache et sérialisation", "order": 3, "wizard": true}, + "network": {"name": "Réseau", "description": "Protocoles réseau", "order": 4, "wizard": true}, + "text": {"name": "Texte", "description": "Encodage et i18n", "order": 5, "wizard": true}, + "xml": {"name": "XML", "description": "Parsing XML", "order": 6, "wizard": true}, + "compression": {"name": "Compression", "description": "Compression de données", "order": 7, "wizard": true}, + "image": {"name": "Image", "description": "Traitement d'images", "order": 8, "wizard": true}, + "crypto": {"name": "Crypto", "description": "Cryptographie", "order": 9, "wizard": true}, + "math": {"name": "Math", "description": "Calculs mathématiques", "order": 10, "wizard": true}, + "system": {"name": "Système", "description": "Interaction système", "order": 11, "wizard": true}, + "debug": {"name": "Debug", "description": "Développement", "order": 12, "wizard": true}, + "data": {"name": "Données", "description": "Formats de données", "order": 13, "wizard": false}, + "async": {"name": "Async", "description": "Programmation asynchrone", "order": 14, "wizard": false}, + "misc": {"name": "Autres", "description": "Extensions diverses", "order": 15, "wizard": false} + }, + "extensions": { + "opcache": {"name": "OPcache", "category": "core", "filename": "opcache.so", "default": true, "wizard": true}, + "session": {"name": "Session", "category": "core", "filename": "session.so", "default": true, "wizard": true}, + "filter": {"name": "Filter", "category": "core", "filename": "filter.so", "default": true, "wizard": true}, + "fileinfo": {"name": "Fileinfo", "category": "core", "filename": "fileinfo.so", "default": true, "wizard": true}, + "tokenizer": {"name": "Tokenizer", "category": "core", "filename": "tokenizer.so", "default": true, "wizard": true}, + "phar": {"name": "Phar", "category": "core", "filename": "phar.so", "default": true, "wizard": true}, + "ctype": {"name": "Ctype", "category": "core", "filename": "ctype.so", "default": true, "wizard": true}, + + "pdo": {"name": "PDO", "category": "database", "filename": "pdo.so", "default": true, "wizard": true}, + "pdo_mysql": {"name": "PDO MySQL", "category": "database", "filename": "pdo_mysql.so", "default": true, "wizard": true, "dependencies": ["pdo", "mysqlnd"]}, + "mysqli": {"name": "MySQLi", "category": "database", "filename": "mysqli.so", "default": true, "wizard": true, "dependencies": ["mysqlnd"]}, + "mysqlnd": {"name": "MySQLnd", "category": "database", "filename": "mysqlnd.so", "default": true, "wizard": true}, + "pdo_sqlite": {"name": "PDO SQLite", "category": "database", "filename": "pdo_sqlite.so", "default": true, "wizard": true, "dependencies": ["pdo"]}, + "sqlite3": {"name": "SQLite3", "category": "database", "filename": "sqlite3.so", "default": true, "wizard": true}, + "pdo_odbc": {"name": "PDO ODBC", "category": "database", "filename": "pdo_odbc.so", "default": false, "wizard": false, "dependencies": ["pdo"]}, + "odbc": {"name": "ODBC", "category": "database", "filename": "odbc.so", "default": false, "wizard": false}, + "ldap": {"name": "LDAP", "category": "database", "filename": "ldap.so", "default": false, "wizard": true}, + "dba": {"name": "DBA", "category": "database", "filename": "dba.so", "default": false, "wizard": false}, + "redis": {"name": "Redis", "category": "database", "filename": "redis.so", "default": false, "wizard": true, "dependencies": ["igbinary"]}, + "memcached": {"name": "Memcached", "category": "database", "filename": "memcached.so", "default": false, "wizard": true, "dependencies": ["igbinary", "msgpack"]}, + "mongodb": {"name": "MongoDB", "category": "database", "filename": "mongodb.so", "default": false, "wizard": true}, + + "apcu": {"name": "APCu", "category": "cache", "filename": "apcu.so", "default": false, "wizard": true}, + "igbinary": {"name": "Igbinary", "category": "cache", "filename": "igbinary.so", "default": false, "wizard": true}, + "msgpack": {"name": "MsgPack", "category": "cache", "filename": "msgpack.so", "default": false, "wizard": true, "dependencies": ["session"]}, + + "curl": {"name": "cURL", "category": "network", "filename": "curl.so", "default": true, "wizard": true}, + "openssl": {"name": "OpenSSL", "category": "network", "filename": "openssl.so", "default": true, "wizard": true}, + "sockets": {"name": "Sockets", "category": "network", "filename": "sockets.so", "default": true, "wizard": true}, + "ftp": {"name": "FTP", "category": "network", "filename": "ftp.so", "default": false, "wizard": true}, + "ssh2": {"name": "SSH2", "category": "network", "filename": "ssh2.so", "default": false, "wizard": true}, + "soap": {"name": "SOAP", "category": "network", "filename": "soap.so", "default": false, "wizard": true}, + "snmp": {"name": "SNMP", "category": "network", "filename": "snmp.so", "default": false, "wizard": false}, + + "mbstring": {"name": "Mbstring", "category": "text", "filename": "mbstring.so", "default": true, "wizard": true}, + "iconv": {"name": "Iconv", "category": "text", "filename": "iconv.so", "default": true, "wizard": true}, + "intl": {"name": "Intl", "category": "text", "filename": "intl.so", "default": true, "wizard": true}, + "gettext": {"name": "Gettext", "category": "text", "filename": "gettext.so", "default": false, "wizard": true}, + + "xml": {"name": "XML", "category": "xml", "filename": "xml.so", "default": true, "wizard": true}, + "dom": {"name": "DOM", "category": "xml", "filename": "dom.so", "default": true, "wizard": true}, + "simplexml": {"name": "SimpleXML", "category": "xml", "filename": "simplexml.so", "default": true, "wizard": true}, + "xmlreader": {"name": "XMLReader", "category": "xml", "filename": "xmlreader.so", "default": true, "wizard": true}, + "xmlwriter": {"name": "XMLWriter", "category": "xml", "filename": "xmlwriter.so", "default": true, "wizard": true}, + "xsl": {"name": "XSL", "category": "xml", "filename": "xsl.so", "default": false, "wizard": true, "dependencies": ["dom"]}, + + "zip": {"name": "Zip", "category": "compression", "filename": "zip.so", "default": true, "wizard": true}, + "zlib": {"name": "Zlib", "category": "compression", "filename": "zlib.so", "default": true, "wizard": true}, + "bz2": {"name": "Bzip2", "category": "compression", "filename": "bz2.so", "default": false, "wizard": true}, + "zstd": {"name": "Zstd", "category": "compression", "filename": "zstd.so", "default": false, "wizard": true}, + "brotli": {"name": "Brotli", "category": "compression", "filename": "brotli.so", "default": false, "wizard": true}, + "lzf": {"name": "LZF", "category": "compression", "filename": "lzf.so", "default": false, "wizard": false}, + + "gd": {"name": "GD", "category": "image", "filename": "gd.so", "default": true, "wizard": true}, + "exif": {"name": "EXIF", "category": "image", "filename": "exif.so", "default": true, "wizard": true}, + + "sodium": {"name": "Sodium", "category": "crypto", "filename": "sodium.so", "default": true, "wizard": true}, + "gnupg": {"name": "GnuPG", "category": "crypto", "filename": "gnupg.so", "default": false, "wizard": false}, + "scrypt": {"name": "Scrypt", "category": "crypto", "filename": "scrypt.so", "default": false, "wizard": false}, + "base58": {"name": "Base58", "category": "crypto", "filename": "base58.so", "default": false, "wizard": false}, + + "bcmath": {"name": "BCMath", "category": "math", "filename": "bcmath.so", "default": true, "wizard": true}, + "gmp": {"name": "GMP", "category": "math", "filename": "gmp.so", "default": false, "wizard": true}, + "ffi": {"name": "FFI", "category": "math", "filename": "ffi.so", "default": false, "wizard": false}, + + "pcntl": {"name": "PCNTL", "category": "system", "filename": "pcntl.so", "default": false, "wizard": true}, + "posix": {"name": "POSIX", "category": "system", "filename": "posix.so", "default": false, "wizard": true}, + "readline": {"name": "Readline", "category": "system", "filename": "readline.so", "default": false, "wizard": true}, + "shmop": {"name": "Shmop", "category": "system", "filename": "shmop.so", "default": false, "wizard": false}, + "sysvmsg": {"name": "SysV Msg", "category": "system", "filename": "sysvmsg.so", "default": false, "wizard": false}, + "sysvsem": {"name": "SysV Sem", "category": "system", "filename": "sysvsem.so", "default": false, "wizard": false}, + "sysvshm": {"name": "SysV Shm", "category": "system", "filename": "sysvshm.so", "default": false, "wizard": false}, + "inotify": {"name": "Inotify", "category": "system", "filename": "inotify.so", "default": false, "wizard": false}, + "uuid": {"name": "UUID", "category": "system", "filename": "uuid.so", "default": false, "wizard": false}, + + "xdebug": {"name": "Xdebug", "category": "debug", "filename": "xdebug.so", "default": false, "wizard": true, "zend_extension": true}, + "pcov": {"name": "PCOV", "category": "debug", "filename": "pcov.so", "default": false, "wizard": false}, + "ast": {"name": "AST", "category": "debug", "filename": "ast.so", "default": false, "wizard": false}, + "vld": {"name": "VLD", "category": "debug", "filename": "vld.so", "default": false, "wizard": false}, + "xhprof": {"name": "XHProf", "category": "debug", "filename": "xhprof.so", "default": false, "wizard": false}, + "excimer": {"name": "Excimer", "category": "debug", "filename": "excimer.so", "default": false, "wizard": false}, + + "yaml": {"name": "YAML", "category": "data", "filename": "yaml.so", "default": false, "wizard": true}, + "simdjson": {"name": "SimdJSON", "category": "data", "filename": "simdjson.so", "default": false, "wizard": true}, + "protobuf": {"name": "Protobuf", "category": "data", "filename": "protobuf.so", "default": false, "wizard": false}, + "ds": {"name": "DS", "category": "data", "filename": "ds.so", "default": false, "wizard": false}, + "csv": {"name": "CSV", "category": "data", "filename": "csv.so", "default": false, "wizard": false}, + "json_post": {"name": "JSON Post", "category": "data", "filename": "json_post.so", "default": false, "wizard": false}, + "jsonpath": {"name": "JSONPath", "category": "data", "filename": "jsonpath.so", "default": false, "wizard": false}, + "var_representation": {"name": "Var Rep", "category": "data", "filename": "var_representation.so", "default": false, "wizard": false}, + "psr": {"name": "PSR", "category": "data", "filename": "psr.so", "default": false, "wizard": false}, + + "swoole": {"name": "Swoole", "category": "async", "filename": "swoole.so", "default": false, "wizard": false}, + "ev": {"name": "Ev", "category": "async", "filename": "ev.so", "default": false, "wizard": false, "dependencies": ["sockets"]}, + "event": {"name": "Event", "category": "async", "filename": "event.so", "default": false, "wizard": false, "dependencies": ["sockets"]}, + + "tidy": {"name": "Tidy", "category": "misc", "filename": "tidy.so", "default": false, "wizard": true}, + "calendar": {"name": "Calendar", "category": "misc", "filename": "calendar.so", "default": false, "wizard": false}, + "timezonedb": {"name": "Timezonedb", "category": "misc", "filename": "timezonedb.so", "default": false, "wizard": false}, + "maxminddb": {"name": "MaxMindDB", "category": "misc", "filename": "maxminddb.so", "default": false, "wizard": false}, + "mailparse": {"name": "Mailparse", "category": "misc", "filename": "mailparse.so", "default": false, "wizard": false, "dependencies": ["mbstring"]}, + "oauth": {"name": "OAuth", "category": "misc", "filename": "oauth.so", "default": false, "wizard": false}, + "amqp": {"name": "AMQP", "category": "misc", "filename": "amqp.so", "default": false, "wizard": false}, + "apfd": {"name": "APFD", "category": "misc", "filename": "apfd.so", "default": false, "wizard": false}, + "bitset": {"name": "Bitset", "category": "misc", "filename": "bitset.so", "default": false, "wizard": false}, + "geospatial": {"name": "Geospatial", "category": "misc", "filename": "geospatial.so", "default": false, "wizard": false}, + "parle": {"name": "Parle", "category": "misc", "filename": "parle.so", "default": false, "wizard": false}, + "quickhash": {"name": "Quickhash", "category": "misc", "filename": "quickhash.so", "default": false, "wizard": false}, + "sync": {"name": "Sync", "category": "misc", "filename": "sync.so", "default": false, "wizard": false}, + "trader": {"name": "Trader", "category": "misc", "filename": "trader.so", "default": false, "wizard": false}, + "translit": {"name": "Translit", "category": "misc", "filename": "translit.so", "default": false, "wizard": false}, + "uploadprogress": {"name": "UploadProgress", "category": "misc", "filename": "uploadprogress.so", "default": false, "wizard": false}, + "xattr": {"name": "Xattr", "category": "misc", "filename": "xattr.so", "default": false, "wizard": false}, + "xlswriter": {"name": "Xlswriter", "category": "misc", "filename": "xlswriter.so", "default": false, "wizard": false} + } +} diff --git a/spk/php84/src/php84.png b/spk/php84/src/php84.png new file mode 100644 index 00000000000..672ac4f6d8c Binary files /dev/null and b/spk/php84/src/php84.png differ diff --git a/spk/php84/src/service-setup.sh b/spk/php84/src/service-setup.sh new file mode 100644 index 00000000000..cf5b930eea1 --- /dev/null +++ b/spk/php84/src/service-setup.sh @@ -0,0 +1,266 @@ +#!/bin/bash +# PHP 8.4 Service Setup Script for spksrc framework + +# Service command - must include LD_LIBRARY_PATH for bundled libraries +# and all arguments in a single command +SERVICE_COMMAND="env LD_LIBRARY_PATH=${SYNOPKG_PKGDEST}/lib ${SYNOPKG_PKGDEST}/sbin/php-fpm -c ${SYNOPKG_PKGVAR}/etc/php.ini -y ${SYNOPKG_PKGVAR}/etc/php-fpm.conf" + +# PID file location (PHP-FPM writes its own PID file) +PID_FILE="${SYNOPKG_PKGVAR}/run/php-fpm.pid" + +# PHP-FPM daemonizes itself, no background needed +SVC_BACKGROUND= + +# Don't write PID - PHP-FPM handles it +SVC_WRITE_PID= + +# Extension directory +EXT_DIR="${SYNOPKG_PKGDEST}/lib/php/extensions/no-debug-non-zts-20240924" + +# Post-installation actions - create configuration files +service_postinst() { + # Create directories + mkdir -p "${SYNOPKG_PKGVAR}/etc" + mkdir -p "${SYNOPKG_PKGVAR}/etc/conf.d" + mkdir -p "${SYNOPKG_PKGVAR}/etc/php-fpm.d" + mkdir -p "${SYNOPKG_PKGVAR}/log" + mkdir -p "${SYNOPKG_PKGVAR}/run" + mkdir -p "${SYNOPKG_PKGVAR}/tmp" + + # Allow 'http' user (CGI executor) to write extension configs + chmod 777 "${SYNOPKG_PKGVAR}/etc/conf.d" + + # Create php.ini if it doesn't exist + if [ ! -f "${SYNOPKG_PKGVAR}/etc/php.ini" ]; then + cat > "${SYNOPKG_PKGVAR}/etc/php.ini" << PHPINI +[PHP] +; PHP 8.4.15 Configuration for Synology DSM +extension_dir = "${EXT_DIR}" + +engine = On +short_open_tag = Off +precision = 14 +output_buffering = 4096 +zlib.output_compression = Off +implicit_flush = Off +serialize_precision = -1 + +error_reporting = E_ALL & ~E_DEPRECATED & ~E_STRICT +display_errors = Off +display_startup_errors = Off +log_errors = On +error_log = ${SYNOPKG_PKGVAR}/log/php_errors.log +log_errors_max_len = 1024 +ignore_repeated_errors = Off +ignore_repeated_source = Off +report_memleaks = On + +max_execution_time = 60 +max_input_time = 60 +memory_limit = 256M + +variables_order = "GPCS" +request_order = "GP" +register_argc_argv = Off +auto_globals_jit = On +post_max_size = 64M +default_mimetype = "text/html" +default_charset = "UTF-8" + +file_uploads = On +upload_max_filesize = 64M +max_file_uploads = 20 + +doc_root = +user_dir = +enable_dl = Off +cgi.fix_pathinfo = 0 + +date.timezone = "UTC" + +session.save_handler = files +session.save_path = "${SYNOPKG_PKGVAR}/tmp" +session.use_strict_mode = 1 +session.use_cookies = 1 +session.use_only_cookies = 1 +session.name = PHPSESSID +session.auto_start = 0 +session.cookie_lifetime = 0 +session.cookie_path = / +session.cookie_domain = +session.cookie_httponly = 1 +session.cookie_samesite = "Lax" +session.serialize_handler = php +session.gc_probability = 1 +session.gc_divisor = 1000 +session.gc_maxlifetime = 1440 +session.sid_length = 32 +session.sid_bits_per_character = 5 +PHPINI + fi + + # Create php-fpm.conf if it doesn't exist + if [ ! -f "${SYNOPKG_PKGVAR}/etc/php-fpm.conf" ]; then + cat > "${SYNOPKG_PKGVAR}/etc/php-fpm.conf" << FPMCONF +[global] +pid = ${SYNOPKG_PKGVAR}/run/php-fpm.pid +error_log = ${SYNOPKG_PKGVAR}/log/php-fpm.log +log_level = notice +daemonize = yes + +include=${SYNOPKG_PKGVAR}/etc/php-fpm.d/*.conf +FPMCONF + fi + + # Create default pool if it doesn't exist + if [ ! -f "${SYNOPKG_PKGVAR}/etc/php-fpm.d/www.conf" ]; then + cat > "${SYNOPKG_PKGVAR}/etc/php-fpm.d/www.conf" << POOLCONF +[www] +user = ${SYNOPKG_PKGNAME} +group = ${SYNOPKG_PKGNAME} + +listen = ${SYNOPKG_PKGVAR}/run/php-fpm.sock +listen.owner = ${SYNOPKG_PKGNAME} +listen.group = ${SYNOPKG_PKGNAME} +listen.mode = 0666 + +pm = dynamic +pm.max_children = 5 +pm.start_servers = 2 +pm.min_spare_servers = 1 +pm.max_spare_servers = 3 +pm.max_requests = 500 + +slowlog = ${SYNOPKG_PKGVAR}/log/php-fpm.slow.log +request_slowlog_timeout = 30s +catch_workers_output = yes +decorate_workers_output = no +POOLCONF + fi + + # Process wizard selections for extensions + # Complete profile: enable all extensions + if [ "${wizard_profile_complete}" = "true" ]; then + for ext_file in "${EXT_DIR}"/*.so; do + if [ -f "$ext_file" ]; then + ext_name=$(basename "$ext_file" .so) + enable_extension "$ext_name" + fi + done + # Minimal profile + elif [ "${wizard_profile_minimal}" = "true" ]; then + [ "${wizard_min_opcache}" = "true" ] && enable_extension "opcache" + [ "${wizard_min_mbstring}" = "true" ] && enable_extension "mbstring" + [ "${wizard_min_pdo}" = "true" ] && enable_extension "pdo" + [ "${wizard_min_pdo_mysql}" = "true" ] && { enable_extension "mysqlnd"; enable_extension "pdo_mysql"; } + [ "${wizard_min_curl}" = "true" ] && enable_extension "curl" + [ "${wizard_min_openssl}" = "true" ] && enable_extension "openssl" + [ "${wizard_min_gd}" = "true" ] && enable_extension "gd" + # Standard profile (default) + else + [ "${wizard_std_opcache}" = "true" ] && enable_extension "opcache" + [ "${wizard_std_pdo}" = "true" ] && enable_extension "pdo" + [ "${wizard_std_pdo_mysql}" = "true" ] && { enable_extension "mysqlnd"; enable_extension "pdo_mysql"; } + [ "${wizard_std_mbstring}" = "true" ] && enable_extension "mbstring" + [ "${wizard_std_curl}" = "true" ] && enable_extension "curl" + [ "${wizard_std_openssl}" = "true" ] && enable_extension "openssl" + [ "${wizard_std_gd}" = "true" ] && enable_extension "gd" + [ "${wizard_std_tokenizer}" = "true" ] && enable_extension "tokenizer" + [ "${wizard_std_bcmath}" = "true" ] && enable_extension "bcmath" + [ "${wizard_std_xml}" = "true" ] && enable_extension "xml" + [ "${wizard_std_zip}" = "true" ] && enable_extension "zip" + fi +} + +# Enable an extension by creating its .ini file +enable_extension() { + local ext_name="$1" + local ext_file="${ext_name}.so" + local prefix="50" + + # Load order: 20=core, 50=standard, 70=dependent + case "$ext_name" in + session|sockets|mysqlnd|pdo|igbinary) prefix="20" ;; + ev|event|msgpack|mysqli|pdo_mysql|redis|memcached) prefix="70" ;; + esac + + local ini_file="${SYNOPKG_PKGVAR}/etc/conf.d/${prefix}-${ext_name}.ini" + + if [ -f "${EXT_DIR}/${ext_file}" ]; then + case "$ext_name" in + opcache|xdebug) + echo "zend_extension=${ext_file}" > "${ini_file}" + ;; + *) + echo "extension=${ext_file}" > "${ini_file}" + ;; + esac + + # Extension-specific config + case "$ext_name" in + opcache) + cat >> "${ini_file}" << 'OPCACHE' +opcache.enable=1 +opcache.enable_cli=0 +opcache.memory_consumption=128 +opcache.interned_strings_buffer=8 +opcache.max_accelerated_files=10000 +opcache.revalidate_freq=2 +opcache.fast_shutdown=1 +OPCACHE + ;; + apcu) + cat >> "${ini_file}" << 'APCU' +apc.enabled=1 +apc.shm_size=32M +apc.ttl=7200 +apc.enable_cli=0 +APCU + ;; + esac + fi +} + +# Pre-start actions +service_prestart() { + # Ensure directories exist + mkdir -p "${SYNOPKG_PKGVAR}/run" + mkdir -p "${SYNOPKG_PKGVAR}/log" + mkdir -p "${SYNOPKG_PKGVAR}/tmp" + mkdir -p "${SYNOPKG_PKGVAR}/etc/conf.d" + + # Allow 'http' user (CGI executor) to write extension configs + chmod 777 "${SYNOPKG_PKGVAR}/etc/conf.d" + + # Verify configuration exists + if [ ! -f "${SYNOPKG_PKGVAR}/etc/php-fpm.conf" ]; then + echo "ERROR: PHP-FPM configuration not found" + return 1 + fi + + # Test configuration with LD_LIBRARY_PATH + env LD_LIBRARY_PATH="${SYNOPKG_PKGDEST}/lib" \ + "${SYNOPKG_PKGDEST}/sbin/php-fpm" \ + -c "${SYNOPKG_PKGVAR}/etc/php.ini" \ + -y "${SYNOPKG_PKGVAR}/etc/php-fpm.conf" \ + -t > /dev/null 2>&1 + + if [ $? -ne 0 ]; then + echo "ERROR: PHP-FPM configuration test failed" + env LD_LIBRARY_PATH="${SYNOPKG_PKGDEST}/lib" \ + "${SYNOPKG_PKGDEST}/sbin/php-fpm" \ + -c "${SYNOPKG_PKGVAR}/etc/php.ini" \ + -y "${SYNOPKG_PKGVAR}/etc/php-fpm.conf" \ + -t 2>&1 + return 1 + fi + + return 0 +} + +# Post-stop actions +service_poststop() { + rm -f "${PID_FILE}" + rm -f "${SYNOPKG_PKGVAR}/run/php-fpm.sock" 2>/dev/null + return 0 +} diff --git a/spk/php84/src/wizard7/install_uifile.sh b/spk/php84/src/wizard7/install_uifile.sh new file mode 100755 index 00000000000..be26a090bb7 --- /dev/null +++ b/spk/php84/src/wizard7/install_uifile.sh @@ -0,0 +1,228 @@ +#!/bin/bash +# PHP 8.4 Installation Wizard with Conditional Steps +# Based on SynoCommunity/spksrc roundcube pattern + +quote_json() { + sed -e 's|\\|\\\\|g' -e 's|"|\\"|g' | tr '\n' ' ' | tr '\t' ' ' +} + +# Step titles (must match exactly) +PROFILE_STEP="Profil" +MINIMAL_STEP="Extensions Minimal" +STANDARD_STEP="Extensions Standard" +CONFIRM_STEP="Confirmation" + +# Helper JavaScript functions +jsFunction=$(/bin/cat<= 0; i--) { + var step = wizardDialog.getStep(wizardDialog.customuiIds[i]); + if (title === step.headline) { + return step; + } + } + return null; + } + function getSelectedProfile(wizardDialog) { + var profileStep = findStepByTitle(wizardDialog, "${PROFILE_STEP}"); + if (!profileStep) return "standard"; + if (profileStep.getComponent("wizard_profile_minimal").checked) return "minimal"; + if (profileStep.getComponent("wizard_profile_complete").checked) return "complete"; + return "standard"; + } +EOF +) + +# Deactivate function for Profile step +# Sets up navigation: Minimal step -> Confirm OR Standard step depending on profile +getDeactivateProfile() { + DEACTIVATE=$(/bin/cat<Minimal - 7 extensions essentielles
OPcache, PDO, MySQL, cURL, OpenSSL, Mbstring, GD", + "defaultValue": false + }, + { + "key": "wizard_profile_standard", + "desc": "Standard - 11 extensions web (recommande)
+ Tokenizer, BCMath, XML, Zip", + "defaultValue": true + }, + { + "key": "wizard_profile_complete", + "desc": "Complet - Toutes les extensions (~142)
Tout est active. Gerez dans PHP Manager.", + "defaultValue": false + } + ] + } + ] +} +EOF +) + +# Step 2: Minimal Profile Extensions +STEP_MINIMAL=$(/bin/cat<Profil Minimal - 7 extensions essentielles pre-selectionnees. Decochez celles dont vous n'avez pas besoin.", + "subitems": [{"key": "info_minimal", "desc": "", "hidden": true}] + }, + { + "type": "multiselect", + "desc": "Extensions essentielles (les plus utilisees) :", + "subitems": [ + {"key": "wizard_min_opcache", "desc": "OPcache (performance)", "defaultValue": true}, + {"key": "wizard_min_mbstring", "desc": "Mbstring (encodage UTF-8)", "defaultValue": true}, + {"key": "wizard_min_pdo", "desc": "PDO (base de donnees)", "defaultValue": true}, + {"key": "wizard_min_pdo_mysql", "desc": "PDO MySQL", "defaultValue": true}, + {"key": "wizard_min_curl", "desc": "cURL (requetes HTTP)", "defaultValue": true}, + {"key": "wizard_min_openssl", "desc": "OpenSSL (securite/HTTPS)", "defaultValue": true}, + {"key": "wizard_min_gd", "desc": "GD (images)", "defaultValue": true} + ] + } + ] +} +EOF +) + +# Step 3: Standard Profile Extensions +STEP_STANDARD=$(/bin/cat<Profil Standard - 11 extensions web pre-selectionnees. Decochez celles dont vous n'avez pas besoin.", + "subitems": [{"key": "info_standard", "desc": "", "hidden": true}] + }, + { + "type": "multiselect", + "desc": "Extensions web essentielles :", + "subitems": [ + {"key": "wizard_std_opcache", "desc": "OPcache (performance)", "defaultValue": false}, + {"key": "wizard_std_pdo", "desc": "PDO (base de donnees)", "defaultValue": true}, + {"key": "wizard_std_pdo_mysql", "desc": "PDO MySQL", "defaultValue": true}, + {"key": "wizard_std_mbstring", "desc": "Mbstring (encodage UTF-8)", "defaultValue": true}, + {"key": "wizard_std_curl", "desc": "cURL (requetes HTTP)", "defaultValue": true}, + {"key": "wizard_std_openssl", "desc": "OpenSSL (securite/HTTPS)", "defaultValue": true}, + {"key": "wizard_std_gd", "desc": "GD (images)", "defaultValue": true}, + {"key": "wizard_std_tokenizer", "desc": "Tokenizer", "defaultValue": true}, + {"key": "wizard_std_bcmath", "desc": "BCMath (calculs)", "defaultValue": true}, + {"key": "wizard_std_xml", "desc": "XML", "defaultValue": true}, + {"key": "wizard_std_zip", "desc": "Zip", "defaultValue": true} + ] + } + ] +} +EOF +) + +# Step 4: Confirmation +STEP_CONFIRM=$(/bin/cat<Installation prete

Les extensions seront activees selon votre profil.

Apres installation, utilisez PHP 8.4 Manager pour gerer les extensions.

Socket PHP-FPM : /var/packages/php84/var/run/php-fpm.sock", + "subitems": [{"key": "info_final", "desc": "", "hidden": true}] + } + ] +} +EOF +) + +# Output complete wizard JSON +echo "[$STEP_PROFILE,$STEP_MINIMAL,$STEP_STANDARD,$STEP_CONFIRM]" > "${SYNOPKG_TEMP_LOGFILE}"