array(
'titel' => 'Taakdelegatie, van A tot Z',
'label' => 'Organisatie',
'tekst' => 'Het volledige protocol, de beslisboom voor de balie, de functiekaart, het beoordelingsformulier, acht dagstartkaarten, de snelteksten voor het dossier en wat u tegen de patient zegt.',
'pagina' => '/mijn-box/taakdelegatie/',
'los' => '',
),
'water' => array(
'titel' => 'Waterkwaliteitsbeheersplan',
'label' => 'Infectiepreventie',
'tekst' => 'Het beheersplan in stappen, met de controlemomenten, de vastlegging per unit en wat u doet bij een afwijkende uitslag.',
'pagina' => '',
'los' => '',
),
'stoffen' => array(
'titel' => 'Organisatie van gevaarlijke stoffen',
'label' => 'Arbo',
'tekst' => 'Heeft u een abonnement op een stoffendatabase of liet u een risicoanalyse uitvoeren, dan bent u er soms nog niet. Dit onderwerp laat zien wat er daarnaast van u wordt verwacht en welke stappen u zelf nog moet zetten.',
'pagina' => '',
'los' => '',
),
'meldcode' => array(
'titel' => 'Organisatie en implementatie van de Meldcode Kindermishandeling en Huiselijk Geweld',
'label' => 'Organisatie',
'tekst' => 'De vijf stappen vertaald naar de tandartspraktijk, met de aandachtsfunctionaris, de teaminstructie en wat u vastlegt in het dossier.',
'pagina' => '',
'los' => '',
),
'incidenten' => array(
'titel' => 'Organisatie en implementatie van een incidentenmanagementsysteem',
'label' => 'Organisatie',
'tekst' => 'Het meldformulier, de teampresentatie waarmee u het invoert en de route van melding naar opvolging, zodat er ook echt iets mee gebeurt.',
'pagina' => '',
'los' => '',
),
'straling' => array(
'titel' => 'Organisatie van stralingsbescherming',
'label' => 'Straling',
'tekst' => 'Wat de regels voor uw praktijk betekenen, wie welke rol heeft en welke instructie uw medewerkers nodig hebben. Met een controle waarmee u nagaat of uw KEW-dossier compleet is.',
'pagina' => '',
'los' => '',
),
'rie' => array(
'titel' => 'RI&E in stappen',
'label' => 'Arbo',
'tekst' => 'Van inventarisatie naar plan van aanpak, met de termijnen erbij en een overzicht dat laat zien wat er nog openstaat.',
'pagina' => '',
'los' => '',
),
'schoonmaak' => array(
'titel' => 'Schoonmaakplan',
'label' => 'Infectiepreventie',
'tekst' => 'Wie maakt wat schoon, hoe vaak en waarmee. Met de kaarten voor op de werkvloer en een aftekenlijst.',
'pagina' => '',
'los' => '',
),
'zelfinspectie' => array(
'titel' => 'Zelfinspectie',
'label' => 'Kwaliteit',
'tekst' => 'Honderd punten over drie themas, met per punt de onderbouwing en een verwijzing naar de wet of richtlijn. Met een printbaar overzicht en uw actiepunten op volgorde van prioriteit.',
'pagina' => '',
'los' => 'https://praktijkoporde.nl/bereid-u-voor-op-een-inspectie/',
),
'personeelsdossier' => array(
'titel' => 'Personeelsdossier op Orde',
'label' => 'Personeel',
'tekst' => 'Alles wat er per medewerker in het dossier hoort. Elk veld kleurt terwijl u invult, met een tabblad per functiegroep en een overzicht als rapport of als Excel.',
'pagina' => '',
'los' => 'https://praktijkoporde.nl/bbc-tool/',
),
'slimboek' => array(
'titel' => 'Het SLIM-boek',
'label' => 'SLIM',
'tekst' => 'Alle documenten die voor het SLIM-systeem nodig zijn, van sterilisatie tot logistiek en de inkoop van materialen, klaar om in te vullen en te verdelen over het SLIM-team.',
'pagina' => '',
'los' => 'https://praktijkoporde.nl/ebook-slim/',
),
'cursus' => array(
'titel' => 'Basis kwaliteit en veiligheid',
'label' => 'Scholing',
'tekst' => 'Acht onderdelen voor het hele team, van wet en richtlijn tot toezichthouders en tuchtrecht. Met een bewijs van deelname op naam.',
'pagina' => '/cursus-volgen/',
'los' => 'https://praktijkoporde.nl/cursus-kwaliteit-en-veiligheid/',
),
);
}
/* =========================================================================
2. DE PRODUCTEN BIJ PAYPRO
Welk product geeft recht op welke onderwerpen. De Box geeft alles wat
hierboven staat, met uitzondering van de scholing.
========================================================================= */
function poo_box_producten() {
$alles = array_keys( poo_onderwerpen() );
$alles = array_diff( $alles, array( 'cursus' ) ); // scholing hoort buiten de Box
return array(
'box' => array(
'titel' => 'Praktijk op Orde Box',
'onderwerpen'=> array_values( $alles ),
'maanden' => 12,
),
'zelfinspectie' => array(
'titel' => 'Zelfinspectie',
'onderwerpen'=> array( 'zelfinspectie' ),
'maanden' => 12,
),
'personeelsdossier' => array(
'titel' => 'Personeelsdossier op Orde',
'onderwerpen'=> array( 'personeelsdossier' ),
'maanden' => 12,
),
'slimboek' => array(
'titel' => 'E-book SLIM in de tandartspraktijk',
'onderwerpen'=> array( 'slimboek' ),
'maanden' => 12,
),
'cursus' => array(
'titel' => 'Cursus Kwaliteit en Veiligheid',
'onderwerpen'=> array( 'cursus' ),
'maanden' => 12,
),
);
}
/* =========================================================================
3. INSTELLINGEN
========================================================================= */
function poo_instelling( $naam, $standaard = '' ) {
$alles = (array) get_option( 'poo_box_instellingen', array() );
return isset( $alles[ $naam ] ) && '' !== $alles[ $naam ] ? $alles[ $naam ] : $standaard;
}
function poo_instelling_zetten( $naam, $waarde ) {
$alles = (array) get_option( 'poo_box_instellingen', array() );
$alles[ $naam ] = $waarde;
update_option( 'poo_box_instellingen', $alles, false );
}
function poo_geheim() {
$geheim = poo_instelling( 'geheim' );
if ( '' === $geheim ) {
$geheim = wp_generate_password( 40, false, false );
poo_instelling_zetten( 'geheim', $geheim );
}
return $geheim;
}
function poo_meldadres() {
return rest_url( 'praktijkoporde/v1/paypro' ) . '?sleutel=' . poo_geheim();
}
/** Zoekt bij een productcode van PayPro het bijbehorende product op. */
function poo_product_bij_code( $code ) {
if ( '' === (string) $code ) {
return '';
}
foreach ( poo_box_producten() as $sleutel => $product ) {
if ( (string) poo_instelling( 'code_' . $sleutel ) === (string) $code ) {
return $sleutel;
}
}
return '';
}
/* =========================================================================
4. HET SCHERM ONDER INSTELLINGEN
========================================================================= */
add_action( 'admin_menu', function () {
add_options_page( 'Praktijk op Orde', 'Praktijk op Orde', 'manage_options', 'praktijk-op-orde-box', 'poo_instellingenscherm' );
} );
function poo_instellingenscherm() {
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
$producten = poo_box_producten();
$bericht = '';
if ( isset( $_POST['poo_opslaan'] ) && check_admin_referer( 'poo_box_opslaan' ) ) {
poo_instelling_zetten( 'sleutel', sanitize_text_field( wp_unslash( $_POST['poo_sleutel'] ?? '' ) ) );
foreach ( $producten as $sleutel => $product ) {
poo_instelling_zetten( 'code_' . $sleutel, sanitize_text_field( wp_unslash( $_POST[ 'poo_code_' . $sleutel ] ?? '' ) ) );
}
$bericht = 'Opgeslagen.';
}
if ( isset( $_POST['poo_logboek_leegmaken'] ) && check_admin_referer( 'poo_box_opslaan' ) ) {
update_option( 'poo_paypro_logboek', array(), false );
$bericht = 'Het logboek is leeggemaakt.';
}
$apisleutel = poo_instelling( 'sleutel' );
$logboek = (array) get_option( 'poo_paypro_logboek', array() );
$ingevuld = 0;
foreach ( $producten as $sleutel => $product ) {
if ( '' !== poo_instelling( 'code_' . $sleutel ) ) {
$ingevuld++;
}
}
$klaar = ( '' !== $apisleutel && $ingevuld > 0 );
?>
Praktijk op Orde
De sleutel is ingevuld en er zijn van de productcodes bekend.
Kopers van die producten krijgen na een betaling vanzelf toegang.
Vul hieronder de sleutel en de productcodes in. Zolang dat niet gebeurd is, wordt er niemand toegelaten.
time();
}
/** De einddatum van een onderwerp, of een lege tekst. */
function poo_tot_wanneer( $onderwerp, $gebruiker_id = 0 ) {
$gebruiker_id = $gebruiker_id ? $gebruiker_id : get_current_user_id();
$toegang = poo_toegang_van( $gebruiker_id );
return $toegang[ $onderwerp ] ?? '';
}
/* =========================================================================
6. DE MELDING VAN PAYPRO
========================================================================= */
if ( ! function_exists( 'poo_antwoord' ) ) {
/**
* Antwoordt aan PayPro. Zij verwachten de kale tekst ok met status 200.
* WordPress zou er anders aanhalingstekens omheen zetten.
*/
function poo_antwoord( $tekst = 'ok', $status = 200 ) {
status_header( $status );
header( 'Content-Type: text/plain; charset=utf-8' );
echo $tekst; // phpcs:ignore
exit;
}
}
add_action( 'rest_api_init', function () {
register_rest_route( 'praktijkoporde/v1', '/paypro', array(
'methods' => 'POST',
'callback' => 'poo_paypro_melding',
'permission_callback' => '__return_true',
) );
} );
function poo_logboek( $regel ) {
$logboek = get_option( 'poo_paypro_logboek', array() );
// De tijd van uw eigen site, zodat het logboek gelijkloopt met uw klok.
array_unshift( $logboek, current_time( 'Y-m-d H:i' ) . ' | ' . $regel );
update_option( 'poo_paypro_logboek', array_slice( $logboek, 0, 200 ), false );
}
/** De namen van de velden die PayPro stuurde, zonder de inhoud. */
function poo_veldnamen( WP_REST_Request $verzoek ) {
$namen = array_keys( array_merge( (array) $verzoek->get_body_params(), (array) $verzoek->get_query_params() ) );
$namen = array_diff( $namen, array( 'sleutel' ) );
sort( $namen );
return empty( $namen ) ? 'niets ontvangen' : implode( ', ', $namen );
}
function poo_paypro_melding( WP_REST_Request $verzoek ) {
if ( ! hash_equals( poo_geheim(), (string) $verzoek->get_param( 'sleutel' ) ) ) {
poo_logboek( 'Geweigerd: het geheime woord klopt niet.' );
return poo_antwoord( 'nee', 403 );
}
if ( '' === poo_instelling( 'sleutel' ) ) {
poo_logboek( 'Melding ontvangen, maar de API-sleutel ontbreekt nog.' );
return poo_antwoord( 'later', 503 );
}
// PayPro kent twee manieren. Een webhook stuurt JSON in de body, een
// postback stuurt losse velden. Wij herkennen ze aan de inhoud.
$rauw = json_decode( $verzoek->get_body(), true );
if ( is_array( $rauw ) && ! empty( $rauw ) ) {
return poo_verwerk_webhook( $rauw );
}
$type = (string) $verzoek->get_param( 'type' );
$hash = (string) $verzoek->get_param( 'payment_hash' );
$stand = strtolower( (string) $verzoek->get_param( 'period_state' ) );
$product = (string) $verzoek->get_param( 'product_id' );
$id = (string) $verzoek->get_param( 'id' );
if ( '' === get_option( 'poo_velden_gezien', '' ) ) {
update_option( 'poo_velden_gezien', '1', false );
poo_logboek( 'PayPro stuurt deze velden mee: ' . poo_veldnamen( $verzoek ) );
}
// Staat de productcode in de melding en kennen wij hem niet, dan stopt het
// hier. Zo wordt PayPro nooit nagebeld voor producten die hier losstaan.
if ( '' !== $product && '' === poo_product_bij_code( $product ) ) {
return poo_antwoord( 'ok', 200 );
}
if ( '' !== $stand ) {
return poo_verwerk_termijnstatus( $id, $stand );
}
if ( '' !== $hash || '' !== $id ) {
return poo_verwerk_verkoop( $hash, $id );
}
poo_logboek( 'Melding zonder herkenbare gegevens. Type: ' . $type . '. Velden: ' . poo_veldnamen( $verzoek ) );
return poo_antwoord( 'ok', 200 );
}
/**
* Verwerkt een webhook van PayPro. Die komt binnen als JSON.
* De inhoud van de melding wordt nooit geloofd: de betaling wordt
* nagevraagd bij PayPro voordat er iemand wordt toegelaten.
*/
function poo_verwerk_webhook( array $bericht ) {
// De betaling zit soms in een omhulsel.
$betaling = $bericht;
foreach ( array( 'payload', 'resource', 'data' ) as $omhulsel ) {
if ( isset( $bericht[ $omhulsel ] ) && is_array( $bericht[ $omhulsel ] ) ) {
$betaling = $bericht[ $omhulsel ];
break;
}
}
$gebeurtenis = strtolower( (string) ( $bericht['event_type'] ?? $bericht['event'] ?? '' ) );
$stand = strtolower( (string) ( $betaling['state'] ?? $betaling['status'] ?? '' ) );
// Eenmalig vastleggen hoe zo'n melding is opgebouwd, zonder de inhoud.
if ( '2' !== get_option( 'poo_webhook_gezien', '' ) ) {
update_option( 'poo_webhook_gezien', '2', false );
poo_logboek( 'Eerste webhook. Gebeurtenis: ' . ( $gebeurtenis ? $gebeurtenis : 'onbekend' )
. '. Buitenste velden: ' . implode( ', ', array_keys( $bericht ) )
. '. Velden van de betaling: ' . implode( ', ', array_keys( $betaling ) ) );
}
// Een terugbetaling of storno: toegang intrekken.
$eraf = array( 'refunded', 'refund', 'chargeback', 'charged_back', 'canceled', 'cancelled' );
if ( in_array( $stand, $eraf, true ) || false !== strpos( $gebeurtenis, 'refund' ) || false !== strpos( $gebeurtenis, 'chargeback' ) ) {
$nummer = (string) ( $betaling['id'] ?? $betaling['reference'] ?? '' );
if ( '' !== $nummer ) {
return poo_verwerk_termijnstatus( $nummer, 'terugbetaald' );
}
return poo_antwoord( 'ok', 200 );
}
// Alleen bij een betaalde betaling gaan wij verder.
$betaald = array( 'paid', 'completed', 'processed', 'betaald' );
if ( ! in_array( $stand, $betaald, true ) && false === strpos( $gebeurtenis, 'paid' ) ) {
return poo_antwoord( 'ok', 200 );
}
$nummer = (string) ( $betaling['id'] ?? $betaling['reference'] ?? '' );
if ( '' === $nummer ) {
poo_logboek( 'Webhook zonder kenmerk van de betaling ontvangen.' );
return poo_antwoord( 'ok', 200 );
}
// Navragen bij PayPro. Dit is de controle.
$echt = poo_haal_betaling( $nummer );
if ( null === $echt ) {
return poo_antwoord( 'later', 503 );
}
// Welk product is er gekocht? Wij zoeken onze eigen productnummers terug
// in de gegevens van de betaling.
$sleutel = poo_product_in( $echt );
if ( '' === $sleutel ) {
poo_logboek( 'Betaling ' . $nummer . ' hoort bij een product dat hier onbekend is. '
. 'Omschrijving: ' . substr( (string) ( $echt['description'] ?? '' ), 0, 120 ) . '. '
. 'Velden: ' . implode( ', ', array_keys( $echt ) ) . '. '
. 'Verwijzingen: ' . implode( ', ', array_keys( (array) ( $echt['_links'] ?? array() ) ) ) . '. '
. 'Kenmerken: ' . implode( ', ', array_keys( (array) ( $echt['metadata'] ?? array() ) ) ) );
return poo_antwoord( 'ok', 200 );
}
$gegevens = poo_klant_bij( $echt );
$email = sanitize_email( $gegevens['email'] );
if ( ! is_email( $email ) ) {
poo_logboek( 'Betaling ' . $nummer . ' leverde geen bruikbaar mailadres op.' );
return poo_antwoord( 'ok', 200 );
}
poo_geef_toegang( $email, $gegevens['voor'], $gegevens['achter'], $nummer, $sleutel );
return poo_antwoord( 'ok', 200 );
}
/** Haalt iets op bij de nieuwere API van PayPro, met de sleutel als bearer. */
function poo_rest_ophalen( $pad ) {
$antwoord = wp_remote_get( POO_PAYPRO_REST . $pad, array(
'timeout' => 20,
'headers' => array(
'Authorization' => 'Bearer ' . poo_instelling( 'sleutel' ),
'Accept' => 'application/json',
),
) );
if ( is_wp_error( $antwoord ) ) {
poo_logboek( 'PayPro was onbereikbaar: ' . $antwoord->get_error_message() );
return null;
}
$code = (int) wp_remote_retrieve_response_code( $antwoord );
$body = json_decode( wp_remote_retrieve_body( $antwoord ), true );
if ( $code < 200 || $code > 299 || ! is_array( $body ) ) {
poo_logboek( 'PayPro antwoordde met status ' . $code . ' op ' . $pad . '.' );
return null;
}
if ( isset( $body['data'] ) && is_array( $body['data'] ) ) {
$body = $body['data'];
}
return $body;
}
/** Vraagt de betaling op, zodat wij weten dat hij echt bestaat. */
function poo_haal_betaling( $nummer ) {
$echt = poo_rest_ophalen( '/payments/' . rawurlencode( $nummer ) );
if ( null === $echt ) {
poo_logboek( 'PayPro bevestigde betaling ' . $nummer . ' niet.' );
}
return $echt;
}
/**
* Zoekt in de gegevens van een betaling welk van onze producten er is gekocht.
* De productnummers zijn lang genoeg om nooit per ongeluk te passen.
*/
function poo_product_in( array $betaling ) {
$tekst = strtolower( (string) wp_json_encode( $betaling ) );
// Eerst op het productnummer, dat is het meest zeker.
foreach ( poo_box_producten() as $sleutel => $product ) {
$code = trim( (string) poo_instelling( 'code_' . $sleutel ) );
if ( '' !== $code && false !== strpos( $tekst, strtolower( $code ) ) ) {
return $sleutel;
}
}
// Staat het nummer er niet in, dan op een kenmerkend woord uit de naam.
// Dit geldt alleen voor producten waarvan u de productcode heeft ingevuld.
// Een leeg vakje in het instellingenscherm betekent dus: hier niets doen.
$woorden = array(
'box' => 'box',
'zelfinspectie' => array( 'zelfinspectie', 'inspectie' ),
'personeelsdossier' => 'personeelsdossier',
'slimboek' => array( 'slim', 'e-book slim' ),
'cursus' => array( 'cursus', 'kwaliteit en veiligheid' ),
);
foreach ( $woorden as $sleutel => $lijst ) {
if ( '' === trim( (string) poo_instelling( 'code_' . $sleutel ) ) ) {
continue;
}
foreach ( (array) $lijst as $woord ) {
if ( false !== strpos( $tekst, strtolower( $woord ) ) ) {
return $sleutel;
}
}
}
return '';
}
/** Haalt naam en mailadres van de koper op. */
function poo_klant_bij( array $betaling ) {
$uit = array( 'email' => '', 'voor' => '', 'achter' => '' );
// Soms staat de klant al in de betaling.
$klant = array();
if ( isset( $betaling['customer'] ) && is_array( $betaling['customer'] ) ) {
$klant = $betaling['customer'];
} elseif ( ! empty( $betaling['customer'] ) && is_string( $betaling['customer'] ) ) {
$opgehaald = poo_rest_ophalen( '/customers/' . rawurlencode( $betaling['customer'] ) );
$klant = is_array( $opgehaald ) ? $opgehaald : array();
}
foreach ( array( 'email', 'emailaddress', 'email_address' ) as $veld ) {
if ( ! empty( $klant[ $veld ] ) ) { $uit['email'] = (string) $klant[ $veld ]; break; }
if ( ! empty( $betaling[ $veld ] ) ) { $uit['email'] = (string) $betaling[ $veld ]; break; }
}
$uit['voor'] = sanitize_text_field( (string) ( $klant['first_name'] ?? $klant['firstname'] ?? '' ) );
$uit['achter'] = sanitize_text_field( (string) ( $klant['last_name'] ?? $klant['lastname'] ?? '' ) );
if ( '' === $uit['voor'] && '' === $uit['achter'] && ! empty( $klant['company_name'] ) ) {
$uit['voor'] = sanitize_text_field( (string) $klant['company_name'] );
}
return $uit;
}
/** Stelt PayPro een vraag: apikey, command en params. */
function poo_vraag_paypro( array $aanvraag ) {
$antwoord = wp_remote_post( POO_PAYPRO_API, array(
'timeout' => 20,
'body' => array(
'apikey' => poo_instelling( 'sleutel' ),
'command' => 'get_sale',
'params' => wp_json_encode( $aanvraag ),
),
) );
if ( is_wp_error( $antwoord ) ) {
poo_logboek( 'PayPro was onbereikbaar: ' . $antwoord->get_error_message() );
return null;
}
$gegevens = json_decode( wp_remote_retrieve_body( $antwoord ), true );
if ( ! is_array( $gegevens ) ) {
poo_logboek( 'Antwoord van PayPro was onleesbaar.' );
return null;
}
foreach ( array( 'response', 'sale', 'data' ) as $omhulsel ) {
if ( isset( $gegevens[ $omhulsel ] ) && is_array( $gegevens[ $omhulsel ] ) ) {
$gegevens = $gegevens[ $omhulsel ];
}
}
if ( ! isset( $gegevens['current_status'] ) && ! isset( $gegevens['id'] ) ) {
return null;
}
return $gegevens;
}
function poo_haal_verkoop_op( $payment_hash, $verkoop_id = '' ) {
$gegevens = null;
if ( '' !== $payment_hash ) {
$gegevens = poo_vraag_paypro( array( 'payment_hash' => $payment_hash ) );
}
if ( null === $gegevens && '' !== $verkoop_id ) {
$gegevens = poo_vraag_paypro( array( 'sale_id' => $verkoop_id ) );
}
if ( null === $gegevens ) {
poo_logboek( 'PayPro bevestigde de verkoop niet.' );
}
return $gegevens;
}
function poo_verwerk_verkoop( $payment_hash, $verkoop_id = '' ) {
if ( '' === $payment_hash && '' === $verkoop_id ) {
poo_logboek( 'Verkoop zonder payment_hash en zonder bestelnummer ontvangen.' );
return poo_antwoord( 'ok', 200 );
}
$verkoop = poo_haal_verkoop_op( $payment_hash, $verkoop_id );
if ( null === $verkoop ) {
return poo_antwoord( 'later', 503 );
}
$status = strtolower( (string) ( $verkoop['current_status'] ?? '' ) );
$code = (string) ( $verkoop['product_id'] ?? '' );
$email = sanitize_email( (string) ( $verkoop['emailaddress'] ?? '' ) );
$id = (string) ( $verkoop['id'] ?? $verkoop_id );
$voor = sanitize_text_field( (string) ( $verkoop['firstname'] ?? '' ) );
$achter = sanitize_text_field( (string) ( $verkoop['lastname'] ?? '' ) );
$betaald = array( 'completed', 'paid', 'betaald', 'voltooid', 'processed' );
if ( ! in_array( $status, $betaald, true ) ) {
poo_logboek( 'Verkoop ' . $id . ' overgeslagen, status is ' . $status . '.' );
return poo_antwoord( 'ok', 200 );
}
$sleutel = poo_product_bij_code( $code );
if ( '' === $sleutel ) {
poo_logboek( 'Verkoop ' . $id . ' gaat over product ' . $code . ', dat hier onbekend is.' );
return poo_antwoord( 'ok', 200 );
}
if ( ! is_email( $email ) ) {
poo_logboek( 'Verkoop ' . $id . ' heeft een onbruikbaar mailadres.' );
return poo_antwoord( 'ok', 200 );
}
poo_geef_toegang( $email, $voor, $achter, $id, $sleutel );
return poo_antwoord( 'ok', 200 );
}
function poo_geef_toegang( $email, $voor, $achter, $verkoop_id, $productsleutel ) {
$producten = poo_box_producten();
if ( ! isset( $producten[ $productsleutel ] ) ) {
return;
}
$product = $producten[ $productsleutel ];
$gebruiker = get_user_by( 'email', $email );
if ( ! $gebruiker ) {
$gebruiker_id = wp_insert_user( array(
'user_login' => $email,
'user_email' => $email,
'user_pass' => wp_generate_password( 24, true, true ),
'first_name' => $voor,
'last_name' => $achter,
'display_name' => trim( $voor . ' ' . $achter ),
'role' => 'subscriber',
) );
if ( is_wp_error( $gebruiker_id ) ) {
poo_logboek( 'Aanmaken mislukt voor ' . $email . ': ' . $gebruiker_id->get_error_message() );
return;
}
wp_new_user_notification( $gebruiker_id, null, 'user' );
poo_logboek( 'Nieuwe gebruiker ' . $email . ' met ' . $product['titel'] . ' (verkoop ' . $verkoop_id . ').' );
} else {
$gebruiker_id = $gebruiker->ID;
poo_logboek( 'Bestaande gebruiker ' . $email . ' kreeg ' . $product['titel'] . ' erbij (verkoop ' . $verkoop_id . ').' );
}
// Per onderwerp de einddatum bijwerken. Loopt er nog toegang, dan telt
// de nieuwe periode daar bovenop.
$toegang = poo_toegang_van( $gebruiker_id );
foreach ( $product['onderwerpen'] as $onderwerp ) {
$huidig = $toegang[ $onderwerp ] ?? '';
$vanaf = ( $huidig && strtotime( $huidig ) > time() ) ? strtotime( $huidig ) : time();
$toegang[ $onderwerp ] = gmdate( 'Y-m-d', strtotime( '+' . (int) $product['maanden'] . ' months', $vanaf ) );
}
update_user_meta( $gebruiker_id, 'poo_toegang', $toegang );
// De verkoop vastleggen, zodat een terugbetaling later te herleiden is.
$verkopen = get_user_meta( $gebruiker_id, 'poo_verkopen', true );
$verkopen = is_array( $verkopen ) ? $verkopen : array();
$verkopen[ (string) $verkoop_id ] = array(
'product' => $productsleutel,
'onderwerpen' => $product['onderwerpen'],
'datum' => gmdate( 'Y-m-d' ),
);
update_user_meta( $gebruiker_id, 'poo_verkopen', $verkopen );
update_user_meta( $gebruiker_id, 'poo_verkoopnummers', implode( ',', array_keys( $verkopen ) ) );
}
function poo_verwerk_termijnstatus( $verkoop_id, $stand ) {
$intrekken = array( 'terugbetaald', 'terugbetalen', 'wachten_op_terugbetalen', 'geannuleerd', 'chargeback' );
if ( ! in_array( $stand, $intrekken, true ) ) {
return poo_antwoord( 'ok', 200 );
}
$gevonden = get_users( array(
'meta_key' => 'poo_verkoopnummers',
'meta_value' => $verkoop_id,
'meta_compare' => 'LIKE',
'number' => 5,
) );
if ( empty( $gevonden ) ) {
poo_logboek( 'Terugbetaling voor verkoop ' . $verkoop_id . ', bijbehorende gebruiker onvindbaar.' );
return poo_antwoord( 'ok', 200 );
}
foreach ( $gevonden as $gebruiker ) {
$verkopen = get_user_meta( $gebruiker->ID, 'poo_verkopen', true );
$verkopen = is_array( $verkopen ) ? $verkopen : array();
if ( ! isset( $verkopen[ (string) $verkoop_id ] ) ) {
continue;
}
$vervalt = $verkopen[ (string) $verkoop_id ]['onderwerpen'];
unset( $verkopen[ (string) $verkoop_id ] );
// Onderwerpen die via een andere verkoop ook zijn gekocht blijven staan.
$blijft = array();
foreach ( $verkopen as $rest ) {
$blijft = array_merge( $blijft, (array) $rest['onderwerpen'] );
}
$toegang = poo_toegang_van( $gebruiker->ID );
foreach ( $vervalt as $onderwerp ) {
if ( ! in_array( $onderwerp, $blijft, true ) ) {
unset( $toegang[ $onderwerp ] );
}
}
update_user_meta( $gebruiker->ID, 'poo_toegang', $toegang );
update_user_meta( $gebruiker->ID, 'poo_verkopen', $verkopen );
update_user_meta( $gebruiker->ID, 'poo_verkoopnummers', implode( ',', array_keys( $verkopen ) ) );
poo_logboek( 'Toegang ingetrokken voor ' . $gebruiker->user_email . ' (verkoop ' . $verkoop_id . ', ' . $stand . ').' );
}
return poo_antwoord( 'ok', 200 );
}
/* =========================================================================
7. DE TEGELS OP DE OVERZICHTSPAGINA:
========================================================================= */
/** De opmaak van de tegels. Reist mee met de shortcode, zodat hij overal werkt. */
function poo_tegel_opmaak() {
// Bij meerdere tegelblokken op een pagina komt de opmaak een keer mee.
if ( ! empty( $GLOBALS['poo_opmaak_gedaan'] ) ) {
return '';
}
$GLOBALS['poo_opmaak_gedaan'] = true;
$s = 'html body div.poo-tk.poo-tk';
return '';
}
add_shortcode( 'poo_overzicht', function () {
if ( ! is_user_logged_in() ) {
return poo_tegel_opmaak() . '
'
. '
Mijn Praktijk op Orde® Box
'
. '
Log eerst in om uw hulpmiddelen te zien. '
. 'Naar de inlogpagina
'
. '
';
}
$verkoop_box = poo_instelling( 'verkoop_box', '/praktijkopordebox/' );
$naam = trim( (string) wp_get_current_user()->display_name );
$uit = poo_tegel_opmaak() . '
';
$uit .= '
Mijn Praktijk op Orde® Box
';
$uit .= '
' . ( '' !== $naam ? 'Welkom terug, ' . esc_html( $naam ) . '. ' : '' )
. 'Hieronder staan uw onderwerpen, met per onderwerp hoe ver u bent.'
. ' Uitloggen
';
$uit .= '
Zo bewaart u uw werk. Elk onderwerp onthoudt vanzelf wat u invult, op de computer waarop u werkt. '
. 'U sluit het venster en gaat later verder waar u gebleven was. Voor uw kwaliteitsdossier kiest u in het afdrukvenster Opslaan als PDF in plaats van uw printer. '
. 'Gaat u ergens anders verder werken, klik dan onderaan op Meenemen als bestand. Uw praktijkgegevens blijven zo bij u en komen nooit op onze server te staan.
';
$uit .= '
'
. '
1DoorlopenWat de regels vragen, hoe u het inricht en welke keuzes bij uw praktijk passen.
'
. '
2InvoerenU krijgt het bij uw team tussen de oren, met de middelen die daarvoor klaarstaan.
'
. '
3VastleggenWat het team dagelijks nodig heeft hangt aan de muur. De afspraken gaan naar uw kwaliteitssysteem.
'
. '
';
// De onderwerpen in twee groepen: wat u heeft en wat er nog te koop is.
$vanu = '';
$rest = '';
$in_de_box = poo_box_producten()['box']['onderwerpen'];
$rest_bevat_box = false;
foreach ( poo_onderwerpen() as $sleutel => $onderwerp ) {
$label = esc_html( $onderwerp['label'] );
$titel = wp_kses_post( $onderwerp['titel'] );
$tekst = wp_kses_post( $onderwerp['tekst'] );
// Toestand 1: toegang, en de pagina bestaat.
if ( poo_heeft_toegang( $sleutel ) && '' !== $onderwerp['pagina'] ) {
$tot = poo_tot_wanneer( $sleutel );
$vanu .= '
'
. '' . $label . ''
. '' . $titel . ''
. '' . $tekst . ''
. ''
. ''
. 'Nog niet begonnen'
. ''
. 'Openen →'
. ( $tot ? 'Uw toegang loopt tot ' . esc_html( date_i18n( 'j F Y', strtotime( $tot ) ) ) . '' : '' )
. '';
continue;
}
// Toestand 2: toegang, maar het onderwerp is nog in de maak.
if ( poo_heeft_toegang( $sleutel ) ) {
$vanu .= '
'
. 'Binnenkort'
. '' . $titel . ''
. '' . $tekst . ''
. 'U heeft hier toegang toe zodra het klaarstaat.'
. '
';
continue;
}
// Toestand 3: nog geen toegang. De hele tegel wijst naar de plek waar
// u dit hulpmiddel kunt bekijken.
if ( '' !== $onderwerp['los'] ) {
$waarheen = $onderwerp['los'];
$regel = ( 'cursus' === $sleutel ) ? 'Bekijk de cursus →' : 'Bekijk dit hulpmiddel →';
} else {
$waarheen = home_url( $verkoop_box );
$regel = 'Alleen in de Praktijk op Orde Box →';
}
// De scholing valt buiten de Box. Dat zeggen wij er bij dat vakje bij.
$erbuiten = ( 'cursus' === $sleutel )
? '
Dit product behoort niet tot de Praktijk op Orde Box, u schaft het apart aan.'
: '';
if ( in_array( $sleutel, $in_de_box, true ) ) {
$rest_bevat_box = true;
}
$rest .= '
'
. '' . $label . ''
. '' . $titel . ''
. '' . $tekst . ''
. $erbuiten
. '' . $regel . ''
. '';
}
if ( '' !== $vanu ) {
$uit .= '
Uw onderwerpen
';
$uit .= '
' . $vanu . '
';
}
if ( '' !== $rest ) {
$uit .= '
Ook verkrijgbaar
';
$uit .= $rest_bevat_box
? '
Hieronder staat wat u nog kunt aanschaffen. Zodra u dat doet komt het hier bij uw onderwerpen te staan.
'
: '
Deze producten behoren niet tot de Praktijk op Orde® Box, maar kunt u wel separaat aanschaffen.
';
$uit .= '
' . $rest . '
';
}
$uit .= '
Verder bouwen aan kwaliteit en veiligheid
';
$uit .= '
Wilt u dieper op deze onderwerpen ingaan, kijk dan bij de '
. 'cursus Kwaliteit en Veiligheid in de Tandartspraktijk. '
. 'Wilt u structureel iemand die op afstand met u meekijkt, kijk dan bij het '
. 'Op Orde abonnement.
';
$uit .= '
Vragen over een van de onderwerpen? Mail naar [email protected].
';
return $uit . '
' . poo_voortgang_script();
} );
/**
* Leest per tegel uit hoe ver iemand is. Die telling staat in de browser,
* op de computer waarop is gewerkt, net als het invulwerk zelf.
*/
function poo_voortgang_script() {
return '';
}
/* =========================================================================
8. DE CONTROLE OP EEN ONDERWERPPAGINA
U hoeft op die pagina's zelf niets te plaatsen. De code kijkt of het
webadres van de pagina bij een onderwerp hoort. Zo ja, en de bezoeker
heeft er nog recht op, dan wordt de hele inhoud vervangen door uitleg
met een bestelmogelijkheid.
========================================================================= */
/** Welk onderwerp hoort bij de pagina die nu wordt getoond? */
function poo_onderwerp_van_deze_pagina() {
if ( ! is_page() ) {
return '';
}
$hier = trim( (string) wp_parse_url( home_url( add_query_arg( array() ) ), PHP_URL_PATH ), '/' );
foreach ( poo_onderwerpen() as $sleutel => $onderwerp ) {
if ( '' === $onderwerp['pagina'] ) {
continue;
}
if ( trim( $onderwerp['pagina'], '/' ) === $hier ) {
return $sleutel;
}
}
return '';
}
add_filter( 'the_content', function ( $inhoud ) {
if ( ! in_the_loop() || ! is_main_query() ) {
return $inhoud;
}
$sleutel = poo_onderwerp_van_deze_pagina();
if ( '' === $sleutel || poo_heeft_toegang( $sleutel ) ) {
return $inhoud;
}
return poo_slotmelding( $sleutel );
}, 20 );
/** De melding die iemand ziet zolang hij nog recht heeft op een onderwerp. */
function poo_slotmelding( $sleutel ) {
$onderwerpen = poo_onderwerpen();
if ( ! isset( $onderwerpen[ $sleutel ] ) ) {
return '';
}
$onderwerp = $onderwerpen[ $sleutel ];
$verkoop_box = poo_instelling( 'verkoop_box', '/praktijkopordebox/' );
$uit = poo_tegel_opmaak() . '
';
if ( ! is_user_logged_in() ) {
$uit .= '
Inloggen'
. '
' . wp_kses_post( $onderwerp['titel'] ) . ''
. '
Dit onderwerp opent nadat u bent ingelogd. Gebruik het e-mailadres waarmee u heeft besteld.'
. '
Naar de inlogpagina
';
} else {
$acties = '';
if ( '' !== $onderwerp['los'] ) {
$acties .= '
Los bestellen';
$acties .= '
of neem de Box, dan zit dit er ook in';
} else {
$acties .= '
Bekijk de Praktijk op Orde Box';
}
$uit .= '
' . esc_html( $onderwerp['label'] ) . ''
. '
' . wp_kses_post( $onderwerp['titel'] ) . ''
. '
Dit onderwerp hoort bij een aanschaf die op uw account nog ontbreekt. Hieronder leest u hoe u het alsnog krijgt.'
. '
' . $acties . '
';
}
return $uit . '
';
}
/* =========================================================================
9. HET MENU-ITEM DAT MEEBEWEEGT
Zet in Weergave en dan Menu's een gewoon item neer dat naar uw
inlogpagina wijst, het adres dat bovenaan bij POO_INLOGPAGINA staat. Zodra iemand is ingelogd verandert dit item vanzelf van tekst en
van bestemming, en komt er een item Uitloggen achter.
De woorden hieronder mag u wijzigen.
========================================================================= */
define( 'POO_MENU_UITGELOGD', 'Inloggen' );
define( 'POO_MENU_INGELOGD', 'Mijn omgeving' );
define( 'POO_MENU_UITLOGGEN', 'Uitloggen' );
// Zet dit op false om Uitloggen uit het menu te laten. De link staat dan in
// de hoek van uw eigen omgeving, en dat scheelt een plek in het menu.
define( 'POO_MENU_UITLOGKNOP', false );
define( 'POO_MENU_BESTEMMING', '/mijnbox/' );
add_filter( 'wp_nav_menu_objects', function ( $items ) {
$nieuw = array();
$ingelogd = is_user_logged_in();
$inlogpad = trim( POO_INLOGPAGINA, '/' );
foreach ( $items as $item ) {
$pad = trim( (string) wp_parse_url( $item->url, PHP_URL_PATH ), '/' );
if ( $inlogpad !== $pad ) {
$nieuw[] = $item;
continue;
}
// Dit is het inlogitem. Het krijgt een eigen klasse, zodat het
// hieronder als knop kan worden opgemaakt.
$item->classes = array_merge( (array) $item->classes, array( 'poo-menuknop' ) );
if ( $ingelogd ) {
$item->title = POO_MENU_INGELOGD;
$item->url = home_url( POO_MENU_BESTEMMING );
}
$nieuw[] = $item;
if ( ! $ingelogd || ! POO_MENU_UITLOGKNOP ) {
continue;
}
$uit = clone $item;
$uit->ID = 'poo-uitloggen';
$uit->db_id = 0;
$uit->title = POO_MENU_UITLOGGEN;
$uit->url = wp_logout_url( home_url( '/' ) );
$uit->classes = array( 'poo-uitloggen' );
$uit->current = false;
$uit->current_item_ancestor = false;
$uit->current_item_parent = false;
$nieuw[] = $uit;
}
return $nieuw;
}, 20 );
/** Het menu-item Inloggen en Mijn Box als knop, in uw eigen kleuren. */
add_action( 'wp_head', function () {
?>
exists() ) {
return false;
}
return ! user_can( $gebruiker, 'edit_posts' ) && ! user_can( $gebruiker, 'manage_options' );
}
/** Na het inloggen naar de eigen omgeving in plaats van naar het beheer. */
add_filter( 'login_redirect', function ( $naar, $gevraagd, $gebruiker ) {
if ( is_wp_error( $gebruiker ) || ! is_object( $gebruiker ) ) {
return $naar;
}
if ( poo_is_koper( $gebruiker ) ) {
return home_url( POO_NA_INLOG );
}
return $naar;
}, 20, 3 );
/** Na het instellen van een wachtwoord naar uw eigen inlogpagina. */
add_filter( 'lostpassword_redirect', function ( $naar ) {
return home_url( POO_INLOGPAGINA );
}, 20 );
/** De beheeromgeving blijft dicht voor kopers. */
add_action( 'admin_init', function () {
if ( wp_doing_ajax() ) {
return;
}
if ( poo_is_koper() ) {
wp_safe_redirect( home_url( POO_NA_INLOG ) );
exit;
}
} );
/** En de zwarte balk bovenin verdwijnt voor hen. */
add_action( 'after_setup_theme', function () {
if ( poo_is_koper() ) {
show_admin_bar( false );
}
} );
/** Na uitloggen terug naar uw eigen inlogpagina. */
add_action( 'wp_logout', function () {
wp_safe_redirect( home_url( POO_INLOGPAGINA ) );
exit;
} );
/** Het logo boven het inlogscherm wijst naar uw site in plaats van naar WordPress. */
add_filter( 'login_headerurl', function () { return home_url( '/' ); } );
add_filter( 'login_headertext', function () { return 'Praktijk op Orde'; } );
/** Het inlogscherm van WordPress in uw kleuren. */
add_action( 'login_enqueue_scripts', function () {
?>
<?php
} );