Back-End

19 ago, 2013

Usando o modo de manutenção no WordPress

Publicidade

Muitos desenvolvedores WordPress têm a necessidade, vez ou outra, de implementar um sistema que permita ao site entrar em “modo manutenção” seja para inauguração de alguma novidade no site ou para modificação de alguma parte importante do código.

Em meu desenvolvimento sempre usei plug-ins de terceiros que me permitiam tal situação, mas como sou adepto de um “código limpo”, no qual o propósito seja de fácil entendimento e também não gosto de usar muitos plug-ins, chegou uma hora que vi a necessidade de criar meu próprio código, que segue como meu primeiro artigo no iMasters.

O que faremos:

Desenvolveremos uma classe genérica que permita colocar o site em modo de manutenção e tenha configurações personalizadas através do painel administrativo.

Organização dos arquivos e diretórios:

image1

Para uma fácil organização e entendimento é indispensável que se crie uma estrutura que permita o entendimento rápido, assim que visto; para isso criaremos dentro da pasta base de nosso tema a pasta plugin_maintenance e dentro dela a pasta modelo.

Dentro da pasta principal do nosso plugin criaremos o arquivo chamado ClassMaintenance.php e dentro da pasta modelo criaremos o arquivo chamado 503.php.

Agora, para já adiantar essa parte, podemos fazer um include no final no arquivo functions.php.

include 'plugin_maintenance/ClassMaintenance.php';

Iniciando a classe

image2

Abrindo o arquivo ClassMaintenance.php. Vamos montar a seguinte estrutura:

<?php
class ClassMaintenance
{
protected $status, $motivoMaintenance;
protected $dtAtivado, $duracaoSias, $duracaoHoras, $duracaoMinutos;
protected $paginasLiberadas;
protected $liberadosFront, $liberadosBack;

public function __construct()
{
add_action('admin_init',
array( &$this, 'formMaintenance' )
);
add_action('admin_menu',
array( &$this, 'addMenu' )
);

if ($this->getStatus() == TRUE) {
add_filter('loginMessage',
array( &$this, 'loginMessage' )
);
add_action( 'admin_notices',
array( &$this, 'adminNotices' )
);
add_action('wp_loaded',
array( &$this, 'applyMaintenanceMode' )
);
}
}

function formMaintenance()
{
}

function addMenu()
{
}

function loginMessage()
{
}

function adminNotices()
{
}

function applyMaintenanceMode()
{
}
}
$ClassMaintenance = new ClassMaintenance();
?>

Valores padrões

image3

Vamos iniciar realmente a programação definindo os métodos get das nossas variáveis protected da classe e definindo os valores padrões.

Logo abaixo da função __construct() iremos criar a função que servirá para definição dos valores padrões da classe.

function setDefaults()
{
$defaults = array(
'duracaoDias' => '1',
'duracaoHoras' => '0',
'duracaoMinutos' => '0',
'paginasLiberadas' => '',
'motivoMaintenance' => 'Nesse momento nosso site está sendo atualizado e por esse motivo você está vendo essa mensagem.
Tente acessar novamente o site dentro de alguns instantes.
Obrigado pela compreensão.',
'liberadosFront' => 'manage_options',
'liberadosBack' => 'manage_options'
);
return $defaults;
}

E dentro da nossa classe __construct(), logo antes de adicionar os eventos automáticos da nossa classe, iremos definir quais são os valores padrões. A nossa função __construct() ficará assim:

public function __construct()
{
$default = $this->setDefaults();
$optionsClass = get_option( 'configMaintenance' );
$this->status = isset( $optionsClass['status'] ) ? TRUE : FALSE;
$this->dtAtivado = isset( $optionsClass['dtAtivado'] ) ? $optionsClass['dtAtivado'] : '';
$this->duracaoDias = isset( $optionsClass['duracaoDias'] ) ? $optionsClass['duracaoDias'] : $default['duracaoDias'];
$this->duracaoHoras = isset( $optionsClass['duracaoHoras'] ) ? $optionsClass['duracaoHoras'] : $default['duracaoHoras'];
$this->duracaoMinutos = isset( $optionsClass['duracaoMinutos'] ) ? $optionsClass['duracaoMinutos'] : $default['duracaoMinutos'];
$this->paginasLiberadas = isset( $optionsClass['paginasLiberadas'] ) ? $optionsClass['paginasLiberadas'] : $default['paginasLiberadas'];
$this->motivoMaintenance = isset( $optionsClass['motivoMaintenance'] ) ? $optionsClass['motivoMaintenance'] : $default['motivoMaintenance'];
$this->liberadosFront = isset( $optionsClass['liberadosFront'] ) ? $optionsClass['liberadosFront'] : $default['liberadosFront'];
$this->liberadosBack = isset( $optionsClass['liberadosBack'] ) ? $optionsClass['liberadosBack'] : $default['liberadosBack']
;
add_action('admin_init',
array( &$this, 'formMaintenance' )
);
add_action('admin_menu',
array( &$this, 'addMenu' )
);
if ($this->getStatus() == TRUE) {
add_filter('loginMessage',
array( &$this, 'loginMessage' )
);
add_action( 'admin_notices',
array( &$this, 'adminNotices' )
);
add_action('wp_loaded',
array( &$this, 'applyMaintenanceMode' )
);
add_action('after_body',
array( &$this, 'NoticeMaintenancePage' )
);
}
}

Criando o painel administrativo

image4

Dentro da auto inicialização da nossa classe através da função __construct() é adicionado aos hooks ‘admin_init’ e ‘admin_menu’ as funções que serão responsáveis por criar o painel administrativo para controle de nosso modo de manutenção.

add_action('admin_init',
 array( &$this, 'formMaintenance' )
 );
 add_action('admin_menu',
 array( &$this, 'addMenu' )
 );

As funções ‘formMaintenance’ e ‘addMenu’ que já foram iniciadas serão atualizadas e seguirão o esquema:

 

/* #############################################################
 # CRIA A PÁGINA DO PAINEL ADMINITRATIVO
 ############################################################# */
 function formMaintenance()
 {
 add_settings_section(
 'section',
 'Configure o modo de manutenção do site',
 '',
 'configMaintenance'
 );
 add_settings_field(
 'statusMaintenance',
 'Ativar o Modo Manutenção::',
 array( &$this, 'callbackStatusManutencao' ),
 'configMaintenance',
 'section'
 );
 add_settings_field(
 'motivoManutencao',
 'Motivo manutenção:',
 array( &$this, 'callbackMotivoManutencao' ),
 'configMaintenance',
 'section'
 );
 add_settings_field(
 'paginasLiberadas',
 'As seguintes páginas tem acesso livre:',
 array( &$this, 'callbackPaginasLiberadas' ),
 'configMaintenance',
 'section'
 );
 add_settings_field(
 'usuariosLiberados',
 'Quem pode acessar o site:',
 array( &$this, 'callbackUsuariosLiberados' ),
 'configMaintenance',
 'section'
 );
 register_setting(
 'configMaintenance',
 'configMaintenance'
 );
 }
function callbackStatusManutencao()
 {
 $previsao = $this->getRetorno();
 $previsao = $previsao['date'] . ' ás ' . $previsao['time'];
 $html = '<label>';
 $html .= '<input type="checkbox" id="status" name="configMaintenance[status]" value="TRUE" ' . checked( TRUE, $this->status, false ) . '/> Quero ativar';
 $html .= '</label><br/>';
 $html .= '<input type="hidden" name="configMaintenance[dtAtivado]" VALUE="'.current_time('timestamp').'">';
 if ($this->getStatus() == true) {
 $html .= "<br/><span class='description'>A manutenção está programada para ser encerrada em dia $previsao </span>";
 }
 $html .= '<table>
 <tbody>
 <tr>
 <td><strong>Voltar em:</strong></td>
 <td><input type="text" id="duracaoDias" name="configMaintenance[duracaoDias]" value="'.$this->duracaoDias.'" size="4" maxlength="5"> <label for="duracaoDias">Dias</label></td>
 <td><input type="text" id="duracaoHoras" name="configMaintenance[duracaoHoras]" value="'.$this->duracaoHoras.'" size="4" maxlength="5"> <label for="duracaoHoras">Horas</label></td>
 <td><input type="text" id="duracaoMinutos" name="configMaintenance[duracaoMinutos]" value="'.$this->duracaoMinutos.'" size="4" maxlength="5"> <label for="duracaoMinutos">Minutos</label></td>
 </tr>
 </tbody>
 </table>';
 echo $html;
 }
function callbackMotivoManutencao()
 {
 $html = '<textarea id="motivoMaintenance" name="configMaintenance[motivoMaintenance]" cols="80" rows="5" class="large-text">'.$this->getMotivoMaintenance().'</textarea>';
 echo $html;
 }
function callbackPaginasLiberadas()
 {
 $html = '<textarea id="paginasLiberadas" name="configMaintenance[paginasLiberadas]" cols="80" rows="5" class="large-text">'.$this->paginasLiberadas.'</textarea>';
 $html .= '<br/><span class="description">Digite os caminhos que devem estar acessíveis mesmo em modo de manutenção. Separe os vários caminhos com quebras de linha.<br/>Exemplo: Se você quer liberar acesso á pagina <strong>http://site.com/sobre/</strong>, você deve digitar <strong>/sobre/</strong>.<br/>Dica: Se você quiser liberar acesso a página inicial digite <strong>[HOME]</strong>.</span>';
 echo $html;
 }
function callbackUsuariosLiberados()
 {
 $html = '<label>Acessar o site:';
 $html .= '<select id="liberadosFront" name="configMaintenance[liberadosFront]">
 <option value="manage_options" ' . selected( $this->liberadosFront, 'manage_options', false) . '>Ninguém</option>
 <option value="manage_categories" ' . selected( $this->liberadosFront, 'manage_categories', false) . '>Editor</option>
 <option value="publish_posts" ' . selected( $this->liberadosFront, 'publish_posts', false) . '>Autor</option>
 <option value="edit_posts" ' . selected( $this->liberadosFront, 'edit_posts', false) . '>Colaborador</option>
 <option value="read" ' . selected( $this->liberadosFront, 'read', false) . '>Visitante</option>
 </select>';
 $html .= '</label><br />';
 $html .= '<label>Acessar o painel adminitrativo:';
 $html .= '<select id="liberadosBack" name="configMaintenance[liberadosBack]">
 <option value="manage_options" ' . selected( $this->liberadosBack, 'manage_options', false) . '>Ninguém</option>
 <option value="manage_categories" ' . selected( $this->liberadosBack, 'manage_categories', false) . '>Editor</option>
 <option value="publish_posts" ' . selected( $this->liberadosBack, 'publish_posts', false) . '>Autor</option>
 <option value="edit_posts" ' . selected( $this->liberadosBack, 'edit_posts', false) . '>Colaborador</option>
 <option value="read" ' . selected( $this->liberadosBack, 'read', false) . '>Visitante</option>
 </select>';
 $html .= '</label><br />';
 echo $html;
 }
/* #############################################################
 # CRIA UM ITEM DO MENU RELACIONANDO A PÁGINA CRIADA ANTERIORMENTE
 ############################################################# */
 function addMenu()
 {
 add_submenu_page('options-general.php',
 'Config. Manutenção',
 'Config. Manutenção',
 'manage_options',
 'maintenance',
 array(&$this,'telaMaintenance')
 );
 }
function telaMaintenance()
 {
 ?>
 <div class="wrap">
 <div id="icon-options-general" class="icon32"></div>
 <h2>Configurações Gerais</h2>
 <form method="post" action="options.php">
 <?php
 if (array_key_exists('settings-updated', $_GET)) echo '<div style="padding: 10px;" class="updated below-h2">Propriedades alteradas com sucesso</div>';
 settings_fields( 'configMaintenance' );
 do_settings_sections( 'configMaintenance' );
 submit_button();
 ?>
 </form>
 </div>
 <?php }

Com isso, nossa tela de gerenciamento do modo de manutenção já está pronta mais inacessível, pois algumas funções ainda não foram criadas.

Funções públicas (que também são usadas internamente)

image5

Aqui é onde mora o coração do sistema de manutenção. Entre as funções está principalmente a que calcula o tempo para o encerramento do modo de manutenção.

Coloque essas funções abaixo da declaração da função applyMaintenanceMode() que ainda não foi desenvolvida, mas já foi declarada.

public function getStatus($retorno = 'var')
 {
 if ($retorno == 'echo'){
 echo $this->status;
 } else {
 return ($this->status);
 }
 }
public function getAcessoLiberado()
 {
 $optval = $this->liberadosFront;
if ( $optval == 'manage_options' && current_user_can('manage_options') ) { return true; }
 elseif ( $optval == 'manage_categories' && current_user_can('manage_categories') ) { return true; }
 elseif ( $optval == 'publish_posts' && current_user_can('publish_posts') ) { return true; }
 elseif ( $optval == 'edit_posts' && current_user_can('edit_posts') ) { return true; }
 elseif ( $optval == 'read' && current_user_can('read') ) { return true; }
 else { return false; }
 }
public function getMotivoMaintenance($retorno = 'var')
 {
 if ($retorno == 'echo'){
 echo $this->motivoMaintenance;
 } else {
 return ($this->motivoMaintenance);
 }
 }
public function getDtAtivadoMaintenance($retorno = 'var')
 {
 if ($retorno == 'echo'){
 echo $this->dtAtivado;
 } else {
 return ($this->dtAtivado);
 }
 }
public function getRetorno()
 {
 //PEGA OS VALORES INFORMATOS NA CONFIG E CONVERTE PRA MINUTOS
 $delay = 0;
 $delay = $delay + ( intval($this->duracaoDias) * 24 * 60 );
 $delay = $delay + ( intval($this->duracaoHoras) * 60 );
 $delay = $delay + ( intval($this->duracaoMinutos) );
//PEGA O TEMPO EM QUE FOI ATIVADO E CALCULA O TEMPO PRA ACABAR SOMANDO
 //A QUANTIDADE DE MINUTOS EM MANUTENÇÃO E TRANSFORMANDO
 $intTimeActivated = intval($this->dtAtivado);
 $intTimeFinished = $intTimeActivated + ($delay*60);
//PEGA A O TEMPO ATUAL NO FORMATO TIMESTAMP
 $intCurrentTime = current_time('timestamp');
//PEGA A DATA E TEMPO ATUAL NO FORMAT
 $strTryBackDate = date_i18n( get_option('date_format'), $intTimeFinished );
 $strTryBackTime = date_i18n( get_option('time_format'), $intTimeFinished );
//NUMERO EM SEGUNDOS ATÉ ONDE SUPOSTAMENTE A MANUTENÇÃO ACABARÁ
 //APÓS CONVERTE PRA MINUTOS E HORAS
 $intTimeDelta_Seconds = $intTimeFinished - $intCurrentTime;
 $intTimeDelta_Minutes = round(($intTimeDelta_Seconds/(60)), 0);
 $intTimeDelta_Hours = round(($intTimeDelta_Seconds/(60*60)), 1);
if ( $intTimeDelta_Minutes < 0 ) {
 $intTimeDelta_Minutes = 0;
 $intTimeDelta_Hours = 0;
 }
$arrDuration = $this->duracao($intTimeDelta_Minutes);
return array(
 'date' => $strTryBackDate,
 'time' => $strTryBackTime,
 'minutesTotal' => $intTimeDelta_Minutes,
 'hoursTotal' => $intTimeDelta_Hours,
 'calcDays' => $arrDuration['days'],
 'calcHours' => $arrDuration['hours'],
 'calcMins' => $arrDuration['mins'],
 );
 }
private function duracao($minutes)
 {
 $minutes = intval($minutes);
 $vals_arr = array( 'days' => (int) ($minutes / (24*60) ),
 'hours' => $minutes / 60 % 24,
 'mins' => $minutes % 60);
$return_arr = array();
 $is_added = false;
 foreach ($vals_arr as $unit => $amount) {
 $return_arr[$unit] = 0;
if ( ($amount > 0) || $is_added ) {
 $is_added = true;
 $return_arr[$unit] = $amount;
 }
 }
 return $return_arr;
 }

Aplicando o modo manutenção

image6

Se você der uma olhada na função __construct() da classe, vai notar que existem funções que somente serão executadas se o modo manutenção estiver ativo e que no momento ainda não foram criadas. São elas:

if ($this->getStatus() == TRUE) {
add_filter('loginMessage',
array( &$this, 'loginMessage' )
);
add_action( 'admin_notices',
array( &$this, 'adminNotices' )
);
add_action('wp_loaded',
array( &$this, 'applyMaintenanceMode' )
);
}

E pra finalizar essa parte do artigo, elas serão implementadas da seguinte forma: junto com algumas funções paralelas necessárias.

function loginMessage()
 {
 return '<div id="login_error"><p>Atualmente este site se encontra em <strong>MODO DE MANUTENÇÃO</strong>.</p></div>';
 }
 
function adminNotices()
{ ?>
 <div class="error">
 <p>Atualmente o site se encontra em <strong>MODO DE MANUTENÇÃO</strong>. Para retornar ao modo normal mude as configurações <strong><a href="<?php echo get_bloginfo( 'url' ); ?>/wp-admin/admin.php?page=maintenance">CLICANDO AQUI.</a></strong></p>
</div>
 <?php }
 
function applyMaintenanceMode()
{
/* # NUNCA MOSTRAR A PAGINA DE MANUTENÇÃO NO LOGIN E OUTRAS
============================================================= */
if( strstr($_SERVER['PHP_SELF'],'wp-login.php') //WP-ADMIN É VERIFICADO EM OUTRO MOMENTO
|| strstr($_SERVER['PHP_SELF'], 'async-upload.php')
|| strstr(htmlspecialchars($_SERVER['REQUEST_URI']), '/plugins/')
|| strstr($_SERVER['PHP_SELF'], 'upgrade.php')
|| $this->liberarUrl()
){
return; //SAI DA ROTINA
}
/* # NUNCA MOSTRAR A PAGINA DE MANUTENÇÃO EM WP-ADMIN
============================================================= */
if ( is_admin() || strstr(htmlspecialchars($_SERVER['REQUEST_URI']), '/wp-admin/') ) {
if ( !is_user_logged_in() ) {
auth_redirect();
}
if ( $this->usuarioAtualLiberado('backend') ) {
return;
} else {
$this->displayMaintenancePage();
}
} else {
if( $this->usuarioAtualLiberado('frontend') ) {
return;
} else {
$this->displayMaintenancePage();
}
}
}
 
/* #############################################################
# FUNÇÃO QUE TROCA A PAGINA SOLICITADA PELA PAGINA DE MANUTENÇÃO
############################################################# */
function displayMaintenancePage()
{
/* # VERIFICA QUAL PÁGINA SERÁ USADA NO REDIRECT DO VISITANTE
# SE TIVER UM ARQUIVO 503 NA PASTA RAIZ DO TEMPLATE VAI
# USALÁ COMO PÁGINA DE MANUTENÇÃO SE NAO TIVER USA UM MODELO
# QUE ESTÁ LOCALIZADA NA PASTA DO PLUGIN
============================================================= */
$file503 = get_template_directory() . '/503.php';
if (file_exists($file503) == false) {
$file503 = dirname(__FILE__) . '/<b>modelo</b>/503.php';
}
 
/* # DEFINI O HEADER COMO INDISPONIVEL
============================================================= */
header('HTTP/1.0 503 Service Unavailable');
 
/* # MOSTRA A PÁGINA AO VISITANTE
============================================================= */
include($file503);
 
/* # NÃO CONTINUA MAIS NENHUM SCRIPT
============================================================= */
exit();
}
 
/* #############################################################
# FUNÇÃO SECUNDÁRIA QUE LIBERA ALGUMAS URL CADASTRADAS
############################################################# */
function liberarUrl()
{
$urlarray = $this->paginasLiberadas;
$urlarray = preg_replace("/\r|\n/s", ' ', $urlarray); //TRANSFORMA QUEBRA DE LINHAS EM ESPAÇO
$urlarray = explode(' ', $urlarray); //TRANSFORMA A STRING EM UM ARRAY
$oururl = 'http://' . $_SERVER['HTTP_HOST'] . htmlspecialchars($_SERVER['REQUEST_URI']);
foreach ($urlarray as $expath) {
if (!empty($expath)) {
$expath = str_replace(' ', '', $expath);
if (strpos($oururl, $expath) !== false) return true;
if ( (strtoupper($expath) == '[HOME]') && ( trailingslashit(get_bloginfo('url')) == trailingslashit($oururl) ) )    return true;
}
}
return false;
}
&nbsp;
/* #############################################################
# FUNÇÃO SECUNDÁRIA QUE LIBERA ALGUNS USUÁRIOS
############################################################# */
function usuarioAtualLiberado($where)
{
if ($where == 'frontend') {
$optval = $this->liberadosFront;
} elseif ($where == 'backend') {
$optval = $this->liberadosBack;
} else {
return false;
}
 
if ( $optval == 'manage_options' && current_user_can('manage_options') ) { return true; }
elseif ( $optval == 'manage_categories' && current_user_can('manage_categories') ) { return true; }
elseif ( $optval == 'publish_posts' && current_user_can('publish_posts') ) { return true;   }
elseif ( $optval == 'edit_posts' && current_user_can('edit_posts') ) { return true; }
elseif ( $optval == 'read' && current_user_can('read') ) { return true; }
else { return false; }
}

503.php

image7

Esse modelo é pra ser colocado dentro da pasta modelo e será usado sempre que a pasta raiz do tema não tiver um arquivo com o nome “503.php”.

A imaginação é sua para criar um arquivo 503.php na pasta raiz do tema, podendo inclusive usar jquery para criar um contador regressivo.

<!DOCTYPE html>
 <html lang="pt-br">
 <head>
 <meta charset="utf-8">
 <title>Em Manutenção</title>
 <meta name="viewport" content="width=device-width, initial-scale=1.0">
 <meta name="description" content="">
 <meta name="author" content="">
 </head>
 <body>
 <?php
 global $ClassMaintenance;
 $retorno = $ClassMaintenance->getRetorno();
 $retorno = $retorno['date'].' ás '.$retorno['time'];
 echo "O site está em manutenção. A previsão de retorno é para <strong> $retorno; </strong>";
 ?>
 </body>
 </html>