Pular para o conteúdo principal

Recursos para desenvolvedores

O plugin é extensível de ponta a ponta só com PHP. Ações, categorias, gatilhos, integrações, condições, variáveis de texto, abas de configuração, rotas REST e canais de entrega são todos registráveis por filtros do WordPress — sem editar o core e sem escrever JavaScript.

Há dois caminhos equivalentes:

  1. Helpers (recomendado) — funções globais joinotify_register_*(), carregadas com o plugin.
  2. Filtros — os apply_filters() por baixo dos helpers, quando você precisa de controle total.

Registre suas extensões em um gancho normal, como plugins_loaded ou init.

Faça um backup antes

Vale o mesmo aviso de sempre para código em produção: tenha backup do site antes de instalar código PHP personalizado. O UpdraftPlus resolve isso gratuitamente.

Helpers de tempo de execução​

Chamáveis de qualquer lugar — dentro de um handler de ação, do seu próprio gancho, de um callback REST.

HelperO que faz
joinotify_send_whatsapp_message_text( $sender, $receiver, $message, $delay )Envia uma mensagem de texto; retorna o código HTTP
joinotify_send_whatsapp_message_media( $sender, $receiver, $media_type, $media, $caption, $delay )Envia uma mensagem de mídia; retorna o código HTTP
joinotify_dispatch_notification( $args )Envia por qualquer canal registrado; retorna um Channel_Result
joinotify_replace_placeholders( $message, $payload, $mode )Resolve as variáveis {{ … }} contra um payload
joinotify_prepare_message( $message, $payload )Resolve variáveis e tokens de IA
joinotify_convert_html_to_whatsapp( $message )Converte HTML de texto rico para a formatação do WhatsApp
joinotify_prepare_receiver( $receiver, $payload )Resolve variáveis e normaliza o telefone
joinotify_format_phone( $phone )Valida e formata um telefone
joinotify_get_placeholders( $integration, $trigger, $context )Lista as variáveis disponíveis
joinotify_get_senders()Lista os remetentes registrados
joinotify_get_first_sender()Primeiro remetente conectado
joinotify_is_valid_sender( $sender )Verifica se o remetente é permitido
joinotify_get_setting( $key )Lê uma configuração do plugin
joinotify_get_message_history( $args )Consulta o histórico de mensagens
joinotify_get_workflows( $args )Lista os fluxos
joinotify_get_workflow_content( $post_id )Conteúdo (árvore de nós) de um fluxo
joinotify_get_workflow_context( $post_id )Contexto do fluxo, por exemplo woocommerce
joinotify_find_workflow_item( $content, $item_id )Encontra um nó dentro do fluxo
joinotify_workflow_has_content( $post_id, $type )Verifica se o fluxo tem nós de um tipo
joinotify_proxy_api_text_message_text_endpoint()URL do endpoint de texto da Proxy API
joinotify_proxy_api_media_message_text_endpoint()URL do endpoint de mídia da Proxy API
joinotify_get_proxy_api_key()Chave configurada da Proxy API
// Resolve as variáveis contra o payload e envia pelo primeiro remetente conectado.
$texto = joinotify_replace_placeholders( '{{ wc_billing_first_name }}, seu pedido saiu!', $payload );
$sender = joinotify_get_first_sender();

if ( $sender && joinotify_is_valid_sender( $sender ) ) {
joinotify_send_whatsapp_message_text( $sender, joinotify_format_phone( '11999998888' ), $texto );
}

Registrando extensões​

HelperFiltro correspondente
joinotify_register_action_category( $categoria )Joinotify/Builder/Action_Categories
joinotify_register_action( $definicao )Joinotify/Builder/Actions
joinotify_register_action_handler( $slug, $cb )Joinotify/Workflow_Processor/Handle_Actions
joinotify_register_action_description( $slug, $cb )Joinotify/Builder/Action_Description
joinotify_register_integration( $integracao )Joinotify/Settings/Tabs/Integrations
joinotify_register_trigger( $contexto, $gatilho )Joinotify/Builder/Get_All_Triggers
joinotify_dispatch_trigger( $hook, $integracao, $payload )disparo em tempo de execução
joinotify_register_conditions( $gatilho, $conds )Joinotify/Validations/Get_Action_Conditions
joinotify_register_condition_operators( $tipo, $ops )Joinotify/Conditions/Check_Condition_Type
joinotify_register_condition_value( $tipo, $cb )Joinotify/Conditions/Get_Compare_Value
joinotify_register_placeholders( $integracao, $vars )Joinotify/Builder/Placeholders_List
joinotify_register_dynamic_placeholder( $regex, $cb )Joinotify/Builder/Resolve_Dynamic_Token
joinotify_register_settings_tab( $aba )Joinotify/Admin/Settings/Section_Tabs
joinotify_register_settings_section( $secao )Joinotify/Admin/Settings/Schema
joinotify_register_rest_route( $rota )Joinotify/Rest/Routes
joinotify_register_notification_channel( $id, $classe )Joinotify/Notifications/Channels

Uma ação personalizada​

Uma ação é um passo que o usuário arrasta para o canvas. Com o settings_schema declarado, o formulário de configuração do nó é renderizado genericamente — nada de frontend.

joinotify_register_action( array(
'action' => 'meu_app_enviar_sms',
'title' => __( 'Meu App: enviar SMS', 'meu-textdomain' ),
'description' => __( 'Envia um SMS pelo Meu App.', 'meu-textdomain' ),
'category' => 'meu_app',
'category_label' => __( 'Meu App', 'meu-textdomain' ), // cria a aba automaticamente
'has_settings' => true,
'is_expansible' => true, // permite encadear ações depois desta
'context' => array(), // vazio = disponível em todos os gatilhos
'default_data' => array(
'action' => 'meu_app_enviar_sms',
'title' => __( 'Meu App: enviar SMS', 'meu-textdomain' ),
'to' => '{{ wc_billing_phone }}',
'message' => '',
),
'settings_schema' => array(
array( 'key' => 'to', 'label' => __( 'Destinatário', 'meu-textdomain' ), 'component' => 'input', 'required' => true ),
array( 'key' => 'message', 'label' => __( 'Mensagem', 'meu-textdomain' ), 'component' => 'textarea', 'required' => true, 'rows' => 4 ),
),
'handler' => function( $action_data, $action, $post_id, $event_data ) {
$to = joinotify_replace_placeholders( $action_data['to'] ?? '', $event_data );
$msg = joinotify_replace_placeholders( $action_data['message'] ?? '', $event_data );

// ... chame aqui o seu gateway de SMS ...

return true; // o handler deve retornar bool
},
) );

// A descrição que aparece sob o título do nó no canvas.
joinotify_register_action_description( 'meu_app_enviar_sms', function( $data, $workflow_action ) {
return esc_html( sprintf( __( 'SMS para %s', 'meu-textdomain' ), $data['to'] ?? '' ) );
} );

Componentes aceitos em settings_schema[].component: input, textarea, number, select (com options), date, time, code, switch, attachments, além de group e repeater aninhados. A chave condition controla visibilidade condicional, com os operadores eq, neq, in, not_in, truthy e falsy.

Um gatilho e sua integração​

Um gatilho só aparece no construtor quando a integração correspondente está registrada e ativada (a opção em setting_key precisa estar em yes).

joinotify_register_integration( array(
'slug' => 'meu_app',
'title' => __( 'Meu App', 'meu-textdomain' ),
'description' => __( 'Automatize mensagens a partir de eventos do Meu App.', 'meu-textdomain' ),
'setting_key' => 'enable_meu_app_integration',
) );

joinotify_register_trigger( 'meu_app', array(
'data_trigger' => 'meu_app_pedido_pago',
'title' => __( 'Meu App: pedido pago', 'meu-textdomain' ),
'description' => __( 'Dispara quando um pedido do Meu App é pago.', 'meu-textdomain' ),
) );

// Dispare a partir do seu próprio gancho.
add_action( 'meu_app_pedido_pago', function( $order_id ) {
joinotify_dispatch_trigger( 'meu_app_pedido_pago', 'meu_app', array( 'order_id' => $order_id ) );
} );

O $payload passado no disparo fica disponível para variáveis e condições no momento do envio.

Novas variáveis de texto​

joinotify_register_placeholders( 'woocommerce', array(
'{{ order_total }}' => array(
'triggers' => array(), // vazio = todos os gatilhos desta integração
'description' => esc_html__( 'Valor total do pedido', 'meu-textdomain' ),
'replacement' => array(
'sandbox' => 'R$ 99,99',
'production' => function( $payload ) {
$order = isset( $payload['order_id'] ) ? wc_get_order( $payload['order_id'] ) : null;

return $order ? wc_price( $order->get_total() ) : '';
},
),
),
) );
  • sandbox é o valor mostrado na pré-visualização do construtor; production é o usado no envio, e pode ser um callable resolvido na hora.
  • triggers lista os data_trigger em que a variável vale. Isso é aplicado também no envio desde a versão 2.1.0: um slug errado faz a variável simplesmente não resolver.

Para variáveis com argumento — {{ meu_app_campo=[total] }} — registre um resolvedor com PCRE:

joinotify_register_dynamic_placeholder( '/\{\{\s*meu_app_campo=\[(.+?)\]\s*\}\}/', function( $matches, $payload ) {
return $payload['fields'][ $matches[1] ] ?? null; // null mantém o token original
} );

Os resolvedores registrados rodam antes dos nativos, então dá para sobrescrever {{ field_id=[…] }}, {{ wc_checkout_field=[…] }}, {{ wc_download_link=[…] }} e {{ user_meta[…] }}.

Condições​

joinotify_register_conditions( 'meu_app_pedido_pago', array(
'meu_app_plano' => array(
'title' => __( 'Plano da assinatura', 'meu-textdomain' ),
'description' => __( 'Verifica o plano do cliente.', 'meu-textdomain' ),
),
) );

joinotify_register_condition_operators( 'meu_app_plano', array( 'is', 'is_not' ) );

joinotify_register_condition_value( 'meu_app_plano', function( $value_map, $type, $payload ) {
return get_post_meta( $payload['order_id'] ?? 0, '_meu_app_plano', true );
} );

Uma rota REST​

joinotify_register_rest_route( array(
'route' => '/admin/meu-app/ping',
'methods' => 'GET',
'callback' => function ( WP_REST_Request $request ) {
return rest_ensure_response( array( 'status' => 'success', 'pong' => true ) );
},
// 'permission' => fn() => current_user_can( 'manage_options' ), // padrão
) );
// → GET /wp-json/joinotify/v1/admin/meu-app/ping

Um canal de entrega​

Um canal implementa Channel_Interface e passa a receber as mensagens dos fluxos. É assim que o WhatsApp Cloud, o Telegram e o e-mail convivem no mesmo plugin.

use MeuMouse\Joinotify\Notifications\Channel_Interface;
use MeuMouse\Joinotify\Notifications\Notification_Message;
use MeuMouse\Joinotify\Notifications\Channel_Result;

class Meu_Canal implements Channel_Interface {

public function get_id() {
return 'meu_canal';
}

public function get_label() {
return __( 'Meu canal', 'meu-textdomain' );
}

public function is_configured() {
return '' !== get_option( 'meu_canal_token', '' );
}

public function get_capabilities() {
return array( 'text', 'media' );
}

public function supports( Notification_Message $message ) {
return '' !== trim( (string) $message->receiver )
&& in_array( $message->type, $this->get_capabilities(), true );
}

public function send( Notification_Message $message ) {
$ok = meu_canal_enviar( $message->receiver, $message->content );

return $ok
? Channel_Result::success( $this->get_id() )
: Channel_Result::failure( $this->get_id(), 'meu_canal_falhou', true );
}
}

joinotify_register_notification_channel( 'meu_canal', Meu_Canal::class );

Notification_Message carrega channel, type, sender, receiver, content, media_type, media_url, caption, attachments, delay, um array context e um meta livre para campos específicos do serviço. Leia o meta com $message->get_meta( $chave ).

Anexos não são um tipo de mensagem

Anexos modificam uma mensagem existente. Não os inclua em get_capabilities() — isso faria supports() recusar mensagens comuns. Verifique $message->attachments em vez disso.

E nunca baixe um link de permissão de download do WooCommerce para reenviar os bytes: cada download consumido é um download a menos para o cliente. Use o path já resolvido, ou mande o link.

O mesmo desenho vale para os códigos de login sem senha, no filtro Joinotify/Otp_Login/Channels.

Payload dos gatilhos nativos​

Cada gatilho nativo entrega um conjunto de parâmetros no array $payload, disponível para variáveis, condições e handlers de ação. Para ler o ID do pedido no gatilho woocommerce_new_order, por exemplo:

$order_id = $payload['order_id'];

Três chaves existem em todos os gatilhos: type (sempre trigger), hook (o gancho que disparou) e integration (o contexto — woocommerce, wordpress, elementor, wpforms…).

WooCommerce​

GatilhoQuando disparaParâmetros adicionais
Novo pedido (woocommerce_new_order)Um pedido é criado, com qualquer statusorder_id
Novo pedido — processando (woocommerce_checkout_order_processed)O pedido entra no status "processando"order_id, status_transition
Pedido concluído (woocommerce_order_status_completed)O status muda para "concluído"order_id, status_transition
Status alterado (woocommerce_order_status_changed)Qualquer mudança de status do pedidoorder_id, old_status, new_status
Reembolso total (woocommerce_order_fully_refunded)O pedido é totalmente reembolsadoorder_id, refund_id
Reembolso parcial (woocommerce_order_partially_refunded)O pedido é parcialmente reembolsadoorder_id, refund_id
Acesso a produto digital liberado (woocommerce_grant_product_download_permissions)O WooCommerce libera o download ao clienteorder_id
Arquivo digital baixado (woocommerce_download_product)O cliente baixa um arquivo do pedidoorder_id, product_id, download_id, user_id, user_email
Entrega de produto digital

Para fluxos de entrega, use Acesso a produto digital liberado. Os links de download só existem a partir desse momento — em gatilhos anteriores as variáveis de download vêm vazias.

WooCommerce Subscriptions​

GatilhoParâmetros adicionais
Assinatura criada (woocommerce_checkout_subscription_created)subscription_id, order_id, recurring_cart
Pagamento concluído (woocommerce_subscription_payment_complete)subscription_id
Pagamento falhou (woocommerce_subscription_payment_failed)subscription_id
Status alterado (woocommerce_subscription_status_active, …_cancelled, …_expired)subscription_id, new_status

WordPress​

GatilhoQuando disparaParâmetros adicionais
Novo registro (user_register)Um usuário se registrauser_id, user_data
Login (wp_login)Um usuário faz loginuser_id, user_data
Solicitação de redefinição (retrieve_password)O usuário pede o link de redefiniçãouser_id, reset_password_link
Senha redefinida (password_reset)O usuário conclui a redefiniçãouser_id
Status do post alterado (transition_post_status)Um post muda de statuspost_id, post_type, post_status, old_post_status

Elementor​

GatilhoParâmetros adicionais
Formulário enviado (elementor_pro/forms/new_record)id, fields, record, handler

WPForms​

GatilhoParâmetros adicionais
Formulário enviado (wpforms_process_complete)id, fields, entry, form_data, entry_id
Pagamento pelo PayPal (wpforms_paypal_standard_process_complete)id, fields, entry, form_data, entry_id

Filtros úteis​

Novos formatos de arquivo​

add_filter( 'Joinotify/Validations/Get_Mime_Types', function( $mime_types ) {
$mime_types['image'][] = 'image/svg+xml';
$mime_types['video'][] = 'video/webm';
$mime_types['document'][] = 'application/epub+zip';
$mime_types['audio'][] = 'audio/flac';

return $mime_types;
} );

Outros pontos​

FiltroPara quê
Joinotify/Builder/Action_Settings_SchemaSobrescrever o schema de uma ação, inclusive nativa
Joinotify/Builder/Action_Default_DataSobrescrever os dados padrão de uma ação
Joinotify/Workflow_Processor/Delaying_ActionsRegistrar uma ação que pausa o fluxo
Joinotify/Workflow_Processor/Branching_ActionsRegistrar uma ação que ramifica o fluxo
Joinotify/Notifications/Default_ChannelTrocar o canal padrão de entrega
Joinotify/Notifications/Message_Before_SendAlterar a mensagem antes do envio
Joinotify/Transport/ActiveForçar o transporte de WhatsApp resolvido
Joinotify/Settings/Integrations/CategoriesRegistrar uma categoria própria na aba Aplicativos

E a ação Joinotify/Notifications/Message_Sent, disparada depois de cada tentativa de envio.

Cuidados​

  • Registre cedo. Use plugins_loaded ou init, antes do bootstrap do construtor.
  • O handler retorna bool. Devolver outra coisa pode interromper o fluxo sem aviso.
  • Contexto do gatilho. O contexto só aparece quando o card da integração está registrado e ativado.
  • Tudo é aditivo. Schemas, dados padrão e resolvedores nativos continuam valendo como fallback quando você não sobrescreve.

Referência completa​

A referência exaustiva da API de extensão, sempre alinhada à versão mais recente, fica no repositório do plugin: