Riepilogo post Febbraio 2018

Ragionando in PHP 7 Lezione 1: Tipizzazione scalare

Postato in PHP 7 | Programmazione PHP

PHP 5 ha introdotto a suo tempo la possibilità per lo sviluppatore di definire verbosamente i tipi di dato richiesti da una funzione. Questo impedisce l'esecuzione di funzioni o metodi richiamati con tipi di dati errati, come ad esempio passare un timestamp UNIX a un metodo che si aspetta un oggetto DateTime. In più, la tipizzazione rende anche chiaro agli altri sviluppatori come utilizzare la funzione in piena compatibilità. Per esempio, qui sotto, si analizzi la definizione delle due funzioni con e senza tipizzazione:

// Senza tipizzazione
function prossimoGiornoSettimana($data) { /*...*/ }
// Con tipizzazione (PHP5)
function prossimoGiornoSettimana(DateTime $data) { /*...*/ }

La tipizzazione era inizialmente limitata alle sole classi e interfacce, ma fu poi ampliata per consentire anche i tipi Array e Callable. PHP 7, invece, estende ulteriormente questo limite consentendo la tipizzazione scalare come int, float, string e bool:

// Tipizzazione scalare (PHP7)
// è possibile definire il tipo richiesto per il parametro $data come INT. E' chiaramente comprensibile allo sviluppatore che questa funzione accetta un timestamp.
function prossimoGiornoSettimana(int $data) { /*...*/ }

Controllo della tipizzazione

Il controllo di tipizzazione per i tipi scalari in PHP 7 funziona in modalità "Coercive", ovvero, nella stessa identica maniera con la quale le vecchie versioni di PHP effettuano il controllo di tipizzazione sulle funzioni built-in. Per meglio spiegare questo concetto, basta pensare alla situazione della funzione floor, che è così definita:

// definizione della funzione float built-in di PHP
float floor ( float $numero )

Quando si passa un parametro alla funzione float, PHP effettuerà una conversione "Coercive" del valore passato alla funzione, se questa è convertibile. Esiste una tabella di conversione per capire se un tipo di variabile può essere automaticamente convertito da PHP in modalità "Coercive":

Tipo di datointfloatstringboolobject
intsisi*si**sino
floatsisisi*sino
stringsisisisisi***
intsisisisino

*: Solo valori che non siano NaN e compresi tra PHP_INT_MIN e PHP_INT_MAX
**: solo se è una stringa numerica
***: solo se l'oggetto ha un metodo magico __toString()

PHP 7 introduce un nuovo metodo per il controllo della tipizzazione, chiamato "Strict", che permette allo sviluppatore di definire, all'inizio dello script, una particolare "frase" che, se presente, imporrà al compilatore di effettuare il controllo di tipizzazione in senso stretto, quindi senza interpolazione dei parametri "Coercive" come sopra spiegato. Attenzione: utilizzare il controllo di tipizzazione Strict farà in modo che anche eventuali funzioni built-in di PHP richiamate all'interno del file saranno sottoposte allo stesso identico trattamento di validazione.

La modalità Strict essenzialmente richiede che tutte le variabili passate alle funzioni tipizzate siano in regola con la definizione della funzione/metodo. In caso contrario, verrà lanciato un TypeError. Ad esempio, in modalità Strict, non è possibile utilizzare stringhe numeriche quando la tipizzazione richiede un float o un int (come invece in modalità Coercive è ammesso e funzionante):

<?php
declare(strict_types=1);
function ciao(string $nome): string {
   echo 'Ciao, ' . $name;
}
function somma(float $a, float $b): float {
   return $a + $b;
}
ciao('Alberto'); // Stampa: "Ciao, Alberto"
ciao(3); // genera un Fatal error: Uncaught TypeError: Argument 1 passed to ciao() must be of the type string, integer given
somma(2, 1); // ritorna "3" anche se i tipi passati sono int
somma(3.2, true); // genera un Fatal error: Uncaught TypeError: Argument 2 passed to somma() must be of the type float, bool given

Poiché la direttiva è impostata per ogni file, e ad inizio di ogni file, è possibile e ammesso combinare le modalità Strict e Coercive nell'applicazione. Ad esempio, se alcune parti della propria applicazione ( magari installate tramite Composer ) non richiedono tipizzazione stretta, mentre invece alcuni specifiche classi della propria applicazione la richiedono, tutta l'applicazione continuerà a funzionare, e il controllo di tipizzazione stretto verrà applicato solo ed esclusivamente sui file che ne fanno uso.

Compatibilità con versioni di PHP precedenti

Non è più possibile, con PHP 7, creare classi denominate int, float, string o bool in quanto queste andrebbero in conflitto con le nuove tipizzazioni di tipo scalare.

Questa era la prima lezione su PHP 7. Naviga nella categoria PHP7 di questo blog per scoprire le altre lezioni e le novità introdotte da PHP7

JSON5 è una nuova versione dello standard JSON, che aggiunge alcune peculiarità alle specifiche originali dello standard, rendendolo un pò più developer-friendly: sono infatti state introdotte alcune novità interessanti, di cui ne cito alcune:

  1. Possibilità di inserire commenti dentro il codice JSON
  2. Compatibilità con i cosiddetti "trailing comma", ovvero, non bisogna dannarsi l'anima per eliminare tutte le virgole in eccesso al termine di un array
  3. Le stringhe possono essere istanziate anche con i single quotes '
  4. I numeri possono essere definiti anche con Infinity, -Infinity, NaN, e -NaN
  5. I numeri possono essere definiti anche come esadecimali

Tutti i dettagli di implementazione sono disponibili sulla pagina ufficiale di JSON5.

Come fare quindi per utilizzare tutte le potenzialità di JSON5 anche su PHP?

Ci viene in aiuto una comoda libreria installabile via composer, colinodell/json5 (link a github)

E' sufficiente integrarla all'interno del vostro progetto con composer:

composer require colinodell/json5

Da questo punto in poi, è possibile utilizzare, al posto delle funzioni json_encode () e json_decode (), i corrispettivi json5_encode () e json5_decode (), che funzionano con gli stessi identici parametri rispetto al passato. Quindi, è possibile effettuare una migrazione di tutto il vostro codice sorgente compatibile con json, semplicemente facendo un search-and-replace globale sulle funzioni json_encode e json_decode, rimpiazzandole con le stringhe nuove.

Ecco un esempio di codice JSON5

{
    foo: 'bar',
    while: true,

    this: 'is a \
multi-line string',

    // this is an inline comment
    here: 'is another', // inline comment

    /* this is a block comment
       that continues on another line */

    hex: 0xDEADbeef,
    half: .5,
    delta: +10,
    to: Infinity,   // and beyond!

    finally: 'a trailing comma',
    oh: [
        "we shouldn't forget",
        'arrays can have',
        'trailing commas too',
    ],
}

Molte volte si presenta la necessità di dover modificare una parte del codice HTML di un sito Wordpress. Le strategie per farlo sono molte, a partire dalla modifica diretta dei file di template, passando per la creazione di child template basati sul template originale, e ancora hook su plugin già presenti per modificarne le funzionalità.

Tuttavia, tutte le opzioni sopra riportate sono scomode, controintuitive, ed esose in termini di tempo.

Consideriamo una modifica molto banale al codice HTML finale di un sito Wordpress, ovvero, la riscrittura di tutte le frasi "Read more" oppure "Error page". Questa necessità capita quasi sempre quando si installa un template da themeforest: il supporto alla traduzione è spesso incompleto o parziale, ed andare ad agire sui file di traduzione è noioso e fa perdere tempo.

Fortunatamente, esiste un metodo per "agguantare" il codice HTML del sito prima che venga mandato in output ai browser client, ricalcolarlo a proprio piacimento, e mandarlo in output modificato così come è stato da noi definito.

Tutto questo è fattibile con un semplicissimo plugin, che incollo qui sotto. Ovviamente, questa soluzione è "quick and dirty". Non è da considerarsi lo stato dell'arte per effettuare modifiche al behaviour del proprio template. E' da usare solo quando si ha fretta, o il template è davvero troppo complicato o vetusto da impedirne una rapida modifica.

<?php
/*
Plugin Name: Final Output Buffer
Plugin URI: https://www.mauriziofonte.it/
Description: Il plugin definitivo per la modifica dell'output HTML di Wordpress.
Version: 1.0
Author: Maurizio Fonte
Author URI: https://www.mauriziofonte.it/
License: Questo plugin è concesso con licenza GPL
*/

function wp_mf_is_login_page ( ) {
    return in_array ( $GLOBALS['pagenow'], array('wp-login.php', 'wp-register.php' ) );
}

function wp_mf_finaloutputbuffer ( $buffer ) {
	
	if ( is_admin() || wp_mf_is_login_page () ) return $buffer;
	
	// correzione di tutti i testi "Read More"
	$buffer = str_ireplace ( 'read more', 'Continua a leggere...', $buffer );
	
	// correzione di tutti i testi "Related posts"
	$buffer = str_ireplace ( 'related posts', 'Articoli correlati', $buffer );
	
	// correzione di tutti i testi "goto homepage"
	$buffer = str_ireplace ( 'goto homepage', 'Ritorna alla Home', $buffer );
	
	// correzione di tutti i testi "error 404"
	$buffer = str_ireplace ( 'error 404', 'Errore 404', $buffer );
	
	return $buffer;
}

function wp_mf_finaloutputbuffer_buffer_start() { 
	if ( ! is_admin() && ! wp_mf_is_login_page () ) ob_start ( "wp_mf_finaloutputbuffer" ); 
}

function wp_mf_finaloutputbuffer_buffer_end() { 
	if ( ! is_admin() && ! wp_mf_is_login_page () ) {
		$html = ob_get_clean ();
		echo $html;
	}
}

add_action ( 'after_setup_theme', 'wp_mf_finaloutputbuffer_buffer_start' );
add_action ( 'shutdown', 'wp_mf_finaloutputbuffer_buffer_end' );

Questo codice va inserito all'interno di un file PHP che chiamerete "final-output-buffer.php", ed inserito dentro una cartella, chiamata anch'essa "final-output-buffer", creata all'interno della cartella "wp-content/plugins" della vostra installazione di Wordpress.

Questo plugin funziona nel seguente modo:

  1. Aggiunge un hook dopo il caricamento del template, wp_mf_finaloutputbuffer_buffer_start
  2. Aggiunge un hook su completamento di tutte le azioni di Wordpress, wp_mf_finaloutputbuffer_buffer_end
  3. Istanzia l'output buffer di PHP dichiarando come callback la funzione wp_mf_finaloutputbuffer

Quindi, il flow di esecuzione è il seguente:

  1. Wordpress fa il setup dei plugin e dei template
  2. A completamento della 1), triggera l'esecuzione di wp_mf_finaloutputbuffer_buffer_start, che controlla che la pagina attualmente "in lavorazione" NON sia una pagina di amministrazione o la pagina di login: nel qual caso, procede come se questo plugin non esistesse, altrimenti, istanzia l'output buffer con callback su wp_mf_finaloutputbuffer
  3. Il sito costruisce il "proprio" HTML ignaro di quello che farà wp_mf_finaloutputbuffer
  4. Il codice HTML "originale" viene agganciato da wp_mf_finaloutputbuffer, che lo ricalcola sulla base del codice PHP di replace che è stato inserito dallo sviluppatore
  5. Viene richiamata la funzione wp_mf_finaloutputbuffer_buffer_end, che manda in output il codice HTML sulla base del ricalcolo della 4)

Per modificare il plugin secondo le proprie necessità, è sufficiente andare a modificare la funzione wp_mf_finaloutputbuffer, andando ad effettuare preg_match, str_replace, o qualsiasi tipo di funzione di manipolazione di stringhe su $buffer, che contiene tutto il codice HTML.

Se nella vostra installazione dovessero già essere presenti dei plugin che sfruttano questa tecnica, è molto probabile che avrete dei problemi di content encoding, o che il sito mostrerà pagina bianca. Se dovesse succedere, è sufficiente rinominare temporaneamente la cartella "final-output-buffer", in modo che Wordpress disattivi il plugin, e andare ad analizzare la situazione successivamente.

Effettuare upgrade a PHP 7.2

Con l'avvento di PHP 7.0 il web è diventato più veloce. Molto più veloce. Oltre a tutte le facilitazioni per lo sviluppatore relative al linguaggio in senso stretto, l'incremento di prestazioni nella major release 7 di PHP è davvero notevole, ed è visibile ad occhio nudo, sia utilizzando CMS molto abusati come Wordpress sia utilizzando codice senza framework o script ad-hoc. Si capisce al primo colpo che la propria applicazione sta performando meglio.

Qui sotto riporto una immagine rappresentativa di test basati solo ed esclusivamente sul profilo computazionale delle versioni di PHP dalla 5.0.5 alla 7.1 effettuate da

sudo add-apt-repository ppa:ondrej/php
sudo apt-get update

Istruzioni per PHP 7.2 per Debian 8 (Jessie) e 9 (Stretch)

sudo apt-get install apt-transport-https lsb-release ca-certificates
sudo wget -O /etc/apt/trusted.gpg.d/php.gpg https://packages.sury.org/php/apt.gpg
echo "deb https://packages.sury.org/php/ $(lsb_release -sc) main" | sudo tee /etc/apt/sources.list.d/php.list
sudo apt-get update

Questi comandi aggiungeranno il repository PPA di Ondřej Surý's, che si occupa di mantenere i pacchetti di PHP 7.2 (oltre a moltre altre versioni) e renderlo disponibile per molte versioni di Linux.

Prima di passare alla installazione vera e propria, però, con i relativi comandi, è necessario fare un pò di attenzione preliminare.

Prima di tutto, create una lista di tutti i pacchetti aggiuntivi di PHP che avete installato sul vostro sistema, come ad esempio php-mbstring o php-curl o php-intl . Questo è necessario perchè poi dovrete reinstallare i corrispettivi in versione 7.2.

In genere, per una normale installazione LAMP, quindi PHP con Apache e Mysql, che supporti la quasi totalità dei requisiti minimi richiesti dai comuni CMS, è necessario installare questi pacchetti: libapache2-mod-php7.2 php7.2 php7.2-common php7.2-gd php7.2-mysql php7.2-imap php7.2-cli php7.2-cgi php7.2-curl php7.2-intl php7.2-pspell php7.2-recode php7.2-sqlite3 php7.2-tidy php7.2-xmlrpc php7.2-xsl php7.2-zip php7.2-mbstring php7.2-soap

Ad ogni modo, io consiglio di operare in questa maniera: dapprima disinstallare tutti i pacchetti di PHP attualmente presenti sul sistema, lasciare che apt ci dica quali pacchetti sta per disinstallare, e poi digitare manualmente la lista di pacchetti da installare andando a leggere quelli che sono stati disinstallati, usando le nuove versioni "-7.2".

Per disinstallare tutto ciò che riguarda PHP dal sistema è sufficiente digitare questo comando:

sudo apt-get remove --purge php*

Poi, analizzare i pacchetti disinstallati, ed installare tutte le rispettive versioni "7.2". Sopra ho riportato la lista di pacchetti di base che vanno bene per la maggior parte dei casi, però, è comunque necessario porre attenzione a quello che si sta facendo, in quanto questo update non è un semplice "aggiorna e dimentica" ma si tratta di un aggiornamento a pacchetti non ufficiali e che potrebbe rendere instabile o non funzionante il vostro ambiente.

sudo apt-get install libapache2-mod-php7.2 php7.2 php7.2-common php7.2-gd php7.2-mysql php7.2-imap php7.2-cli php7.2-cgi php7.2-curl php7.2-intl php7.2-pspell php7.2-recode php7.2-sqlite3 php7.2-tidy php7.2-xmlrpc php7.2-xsl php7.2-zip php7.2-mbstring php7.2-soap

Testare il vostro sistema dopo l'installazione, non solo andando ad aprire sul browser la vostra applicazione, ma andando a leggere i log di errore di PHP e Apache è un obbligo. Lì potrete capire se c'è qualche pezzo del software non compatibile con la nuova versione, o se avete dimenticato un pacchetto aggiuntivo di PHP che ne estende le funzionalità.

Backup automatici con cPanel inviati via FTP

Postato in cPanel

cPanel è uno dei tanti pannelli general-purpose per la gestione completa di server che esistono, assieme ai suoi gemelli Plesk e Webmin. E' un pannello di controllo che si è sempre più affermato nel corso degli anni, e posso dire, per esperienza diretta, avendo in gestione completa due server cPanel WHM, che è un ottimo strumento per aumentare la produttività dei sistemisti e degli sviluppatori.

Ad ogni modo, cPanel contiene anche alcune piccole lacune funzionali: ad esempio, in modalità utente ( quindi non avendo accesso ad un pannello WHM ), il sistema di cPanel non permette la creazione automatica di backup così come invece avviene in modalità root ( da pannello WHM ).

Il sistema di backup automatico previsto da WHM è qualcosa che io reputo fondamentale ed utilissimo, ed è un peccato che lo stesso non sia utilizzabile anche in modalità utente.

Tuttavia, è possibile risolvere il problema con un pò di PHP. Infatti, costruendo ad hoc alcune richieste HTTP da mandare al proprio server cPanel, è possibile "emulare" quello che fareste voi direttamente da interfaccia grafica, facendo di fatto partire le routine di backup che cPanel esegue quando viene fatto partire un backup manuale.

In produzione io uso un comodo script in PHP, che fa proprio questo: si collega al server cPanel "target", e lo obbliga a far partire un backup di tutto l'ambiente inviandolo ad un server remoto FTP.

<?php

/**
 * CP-PHP-Backup-Trigger
 * PHP script to allow periodic cPanel backups automatically, optionally to a remote FTP server.
 *
 * @version 1.0 - Last edited on 25 February 2018
 * @author Maurizio Fonte <fonte.maurizio@gmail.com>
 *
 * @attention: This script contains passwords. KEEP ACCESS TO THIS FILE SECURE! (place it in your home dir, or anywhere NOT publicly available via a virtualhost)
 */

/* 
	CONFIG
	Edit this params to make the system correctly connect to:
	1) Your cPanel account -- CPANEL_ variables
	2) Your remote FTP/SSH server -- BACKUP_ variables
	3) The behaviour of the script -- Verbosity, logging
*/
define ( 'CPANEL_USER', '' );													// your cPanel username
define ( 'CPANEL_PASS', '' );													// your cPanel password
define ( 'CPANEL_HOST', '' );													// the domain name of your cPanel hosting provider
define ( 'CPANEL_SSL', true );													// whether your cPanel hosting provider needs to be accessed via SSL (https) or not
define ( 'CPANEL_THEME', 'paper_lantern' );										// the theme you're actually using in your cPanel account
define ( 'BACKUP_FTP_USER', '' );												// the remote FTP/SSH username
define ( 'BACKUP_FTP_PASS', '' );												// the remote FTP/SSH password
define ( 'BACKUP_FTP_HOST', '' );												// the remote FTP/SSH server
define ( 'BACKUP_FTP_PORT', '' );												// the remote FTP/SSH port
define ( 'BACKUP_FTP_MODE', 'ftp' );											// the modality of which you want let cPanel connect to your remote server ( valid configs are scp | ftp | passiveftp )
define ( 'BACKUP_FTP_REMOTE_DIR', '/' );										// the remote directory where you want the backup file to be stored
define ( 'BACKUP_NOTIFY_EMAIL', '' );											// the notification email upon backup success ( triggered from cPanel ) -- NULL if you want to disable this
define ( 'VERBOSE_OUTPUT', true );												// whether you want or not verbosity of this script
define ( 'OUTPUT_LOG', rtrim ( dirname ( __FILE__ ), DIRECTORY_SEPARATOR ) . DIRECTORY_SEPARATOR . pathinfo ( __FILE__, PATHINFO_FILENAME ) . '.log' );
define ( 'COOKIE_JAR_FILE', rtrim ( dirname ( __FILE__ ), DIRECTORY_SEPARATOR ) . DIRECTORY_SEPARATOR . '.cpanel.temp.cookie' );
/*
	END CONFIG
	From now on, the script does not need any modification
*/

ini_set ( 'max_execution_time', 120 );

$cpanel_url = ( CPANEL_SSL ) ? 'https://' . CPANEL_HOST . ':2083' : 'http://' . CPANEL_HOST . ':2082';
define ( 'CPANEL_FULL_URL', $cpanel_url );

// check stage
if ( ! function_exists ( 'curl_init' ) ) die ( 'Your PHP environment does not have support for Curl functions. Contact your hosting provider. Exiting now...' . chr(10) );
if ( ! function_exists ( 'ftp_connect' ) ) die ( 'Your PHP environment does not have support for FTP functions. Contact your hosting provider. Exiting now...' . chr(10) );
if ( strtolower ( php_sapi_name () ) !== "cli" ) die ( 'You have to call this script from command line. Exiting now...' . chr(10) );
if ( ! file_put_contents ( COOKIE_JAR_FILE, 'TEMP' ) ) die ( 'This script needs write access to current directory ( ' . dirname ( __FILE__ ) . ' ). Double check your environment and retry...' . chr(10) );
touch ( COOKIE_JAR_FILE );
if ( ! empty ( OUTPUT_LOG ) && is_file ( OUTPUT_LOG ) ) unlink ( OUTPUT_LOG );

// stage 1. Check connection to FTP server provided, prior of triggering the automated backup via cPanel
if ( in_array ( BACKUP_FTP_MODE, Array ( 'ftp', 'passiveftp' ) ) && ! ftp_test () ) die ( 'Cannot connect to the remote FTP server you provided. Check configuration and credentials. Exiting now...' . chr(10) );
else if ( in_array ( BACKUP_FTP_MODE, Array ( 'scp' ) ) && ! ssh_test () ) die ( 'Cannot connect to the remote SSH server you provided. Check configuration and credentials. Exiting now...' . chr(10) );

// stage 2. Login Phase + Backup Trigger
if ( ( $security_token = cpanel_login () ) !== false ) {
	
	define ( 'CPANEL_LOGGED_IN_URL', CPANEL_FULL_URL . '/' . trim ( $security_token, '/' ) . '/' );
	
	if ( VERBOSE_OUTPUT ) out ( ' # LOG # successfully logged in into your cPanel hosting provider with security_token=' . trim ( $security_token, '/' ) );
	if ( VERBOSE_OUTPUT ) out ( ' # LOG # Going to trigger the automated FTP backup verbosely...' );
	
	$backup_trigger = exec_curl ( 
		CPANEL_LOGGED_IN_URL . 'frontend/' . CPANEL_THEME . '/backup/dofullbackup.html', 
		Array (
			'dest' => BACKUP_FTP_MODE,
			'email_radio' => ( BACKUP_NOTIFY_EMAIL ) ? '1' : '0',
			'email' => ( BACKUP_NOTIFY_EMAIL ) ? BACKUP_NOTIFY_EMAIL : '',
			'server' => BACKUP_FTP_HOST,
			'user' => BACKUP_FTP_USER,
			'pass' => BACKUP_FTP_PASS,
			'port' => BACKUP_FTP_PORT,
			'rdir' => BACKUP_FTP_REMOTE_DIR
		)
	);
	
	if ( $backup_trigger['status'] ) {
		
		// let's check the output from cPanel...
		$cpanel_backup_status = false;
		$cpanel_backup_message = 'Undefined message';
		if ( strpos ( $backup_trigger['response'], 'backupSuccess' ) !== false ) {
			$pos = strpos ( $backup_trigger['response'], '<div id="backupSuccessMsg"' );
			if ( $pos !== false ) {
				$msg = strip_tags ( substr ( $backup_trigger['response'], $pos, ( strpos ( $backup_trigger['response'], '</div>', $pos+1 ) - $pos ) ) );
				$cpanel_backup_status = true;
				$cpanel_backup_message = $msg;
			}
		}
		else if ( strpos ( $backup_trigger['response'], 'backupFailure' ) !== false ) {
			$pos = strpos ( $backup_trigger['response'], '<div id="backupFailureMsg"' );
			if ( $pos !== false ) {
				$msg = strip_tags ( substr ( $backup_trigger['response'], $pos, ( strpos ( $backup_trigger['response'], '</div>', $pos+1 ) - $pos ) ) );
				$cpanel_backup_message = $msg;
			}
		}
		
		if ( $cpanel_backup_status ) {
			out ( 'CPANEL BACKUP TRIGGERED CORRECTLY!' );
		}
		else {
			out ( '# CPANEL DOFULLBACKUP ERROR #' );
		}
		
		out ( 'Response from cPanel server: ' . trim ( preg_replace ( '/\s\s+/', ' ', str_replace ( Array ( chr(13).chr(10), chr(10), chr(9) ), ' ', $cpanel_backup_message ) ) ) );
	}
	else out ( '# CPANEL DOFULLBACKUP ERROR #. Cannot reliably get a response from the /backup/dofullbackup realm of cPanel. This can be a problem on the hosting provider itself.' );
	
}
else out ( '# CPANEL LOGIN ERROR #. Cannot login with the credentials provided. Also, make sure the CPANEL url you configured ( ' . CPANEL_FULL_URL . ' ) does exist and is reachable via a browser.' );

// finalize
unlink ( COOKIE_JAR_FILE );

function out ( $message ) {
	echo $message . chr(10);
	if ( ! empty ( OUTPUT_LOG ) ) file_put_contents ( OUTPUT_LOG, date ( 'Y-m-d H:i:s' ) . chr(9) . $message . chr(10), FILE_APPEND );
}

function exec_curl ( $url, $post_vars = Array () ) {
	
	if ( VERBOSE_OUTPUT ) out ( ' # LOG # Going to call remote url with cUrl: ' . $url . ' ( post_vars=' . serialize ( $post_vars ) . ' )' );
	
	$ch = curl_init();
	curl_setopt($ch, CURLOPT_URL,            $url);
	curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
	curl_setopt($ch, CURLOPT_FOLLOWLOCATION, true);
	curl_setopt($ch, CURLOPT_USERAGENT,      'Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:58.0) Gecko/20100101 Firefox/58.0' );
	curl_setopt($ch, CURLOPT_REFERER,        CPANEL_FULL_URL );
	if ( ! empty ( $post_vars ) && is_array ( $post_vars ) && count ( array_keys ( $post_vars ) ) > 0 ) {
		curl_setopt($ch, CURLOPT_POST,       true);
		curl_setopt($ch, CURLOPT_POSTFIELDS, http_build_query ( $post_vars ));
	}
	curl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 20);
	curl_setopt($ch, CURLOPT_TIMEOUT,        1200);
	curl_setopt($ch, CURLOPT_COOKIEJAR,      COOKIE_JAR_FILE );
	curl_setopt($ch, CURLOPT_COOKIEFILE,     COOKIE_JAR_FILE );
	
	$result = curl_exec($ch);
	$httpcode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
	
	if ( VERBOSE_OUTPUT ) out ( ' # LOG # cUrl response http_code=' . $http_code . '. Response_length=' . strlen ( $result ) );
	
	curl_close($ch);
	if ( $httpcode === 200 ) return Array ( 'status' => true, 'response' => $result );
	else return Array ( 'status' => false, 'response' => $result );
}

function cpanel_login ( ) {
	
	if ( VERBOSE_OUTPUT ) out ( ' # LOG # Going to initiate login phase with your cPanel hosting provider...' );
	
	$login = exec_curl ( CPANEL_FULL_URL . '/login/?login_only=1', Array ( 'goto_uri' => '/', 'user' => CPANEL_USER, 'pass' => CPANEL_PASS ) );
	if ( $login['status'] && ( $decoded = json_decode ( $login['response'], true ) ) !== false && array_key_exists ( 'security_token', $decoded ) ) {
		return $decoded['security_token'];
	}
	else return false;
}

function ftp_test ( ) {
	
	if ( ! function_exists ( 'ftp_connect' ) ) {
		if ( VERBOSE_OUTPUT ) out ( ' # WARNING # ftp_test() : cannot reliably check if your FTP connection works because your PHP installation is lacking ftp functions support. Skipping this check...' );
		return true;
	}
	
	$conn_id = @ftp_connect ( BACKUP_FTP_HOST, BACKUP_FTP_PORT ); 
	$login_result = @ftp_login ( $conn_id, BACKUP_FTP_USER, BACKUP_FTP_PASS );

	// check connection and login result
	if ( ( !$conn_id ) || ( ! $login_result ) ) {
		ftp_close ( $conn_id );
		return false;
	} 
	else {
		
		if ( VERBOSE_OUTPUT ) out ( ' # LOG # connected to FTP remote server with CONN_ID=' . $conn_id . ', LOGIN_RES=' . $login_result );
		
		if ( BACKUP_FTP_MODE === 'passiveftp' ) ftp_pasv ( $conn_id, true );
		$contents = ftp_rawlist ( $conn_id, '/' );
		
		if ( VERBOSE_OUTPUT ) out ( ' # LOG # remote FTP directory contains ' . count ( array_keys ( $contents ) ) . ' items...' );
		
		ftp_close ( $conn_id );
		return true;
	}
}

function ssh_test ( ) {
	
	if ( ! function_exists ( 'ssh2_connect' ) ) {
		if ( VERBOSE_OUTPUT ) out ( ' # WARNING # ssh_test() : cannot reliably check if your SSH connection works because your PHP installation is lacking ssh2 functions support. Skipping this check...' );
		return true;
	}
	
	$conn = @ssh2_connect ( BACKUP_FTP_HOST, BACKUP_FTP_PORT );
	$res = @ssh2_auth_password ( $conn, BACKUP_FTP_USER, BACKUP_FTP_PASS );
	if ( ! $conn || ! $res ) return false;
	else return true;
}

Istruzioni per l'installazione e la configurazione dei backup automatici

  1. Copia e incolla il codice sorgente riportato in questo articolo ( il codice sorgente è disponibile anche su Github ) in un nuovo file, avendo cura di posizionarlo fuori da qualsiasi webroot, ovvero, in nessuna cartella del server che sia collegata ad un VirtualHost. Su cPanel è sufficiente posizionare il file nella directory superiore rispetto a "public_html"
  2. Modifica le configurazioni dello script per adattarle alla tua situazione
  3. Effettua un test dello script richiamandolo da riga di comando in modalità verbosa ( da configurazione ). Se tutto OK, lo script risponderà con CPANEL BACKUP TRIGGERED CORRECTLY!
  4. Crea un cron per automatizzare il sistema. I cron possono essere comodamente gestiti su cPanel anche da interfaccia grafica.

Qualche nozione di sicurezza sulle configurazioni dello script

Lo script non fa altro che simulare quello che dovrebbe fare un utente tramite l'interfaccia grafica di cPanel, quando si crea manualmente un backup tramite il metodo "dofullbackup" di cPanel. Quindi, tutte le comunicazioni tra il vostro server remoto e il server cPanel vengono di fatto gestite da cPanel.

Proprio per questo motivo, è molto consigliato utilizzare un server FTP target che sia opportunamente configurato per consentire solo connessioni FTP cifrate esplicitamente. Così facendo cPanel, una volta terminata la creazione dell'archivio .tar.gz del vostro server, si collegherà al vostro server FTP senza mandare in chiaro le password FTP nella rete internet.

Dal momento che sarà necessario scrivere su file le proprie password per far funzionare lo script, fare sempre molta attenzione alla visibilità di questo file ad altri account sul sistema, ed utilizzare tutti i metodi a voi conosciuti per impedire l'accesso in lettura allo script da utenti non autorizzati.