policy = $policy;
$this->elementor = $elementor;
$this->scan = $scan;
}
/**
* Haakt aan.
*/
public function register(): void {
add_action( 'template_redirect', array( $this, 'maybe_activate' ), 0 );
// Zonder de paginascan (Pro) is er niemand die de pagina aan het einde
// van het request door het page_output-filter haalt — en dat filter is
// wat de sleuven in de balk invult. Dan doen we het zelf, minimaal.
if ( null === $this->scan ) {
add_action( 'template_redirect', array( $this, 'maybe_buffer' ), 2 );
}
add_action( 'admin_bar_menu', array( $this, 'menu' ), 100 );
// Het rapport als állerlaatste regel, ná de bewerkknop van EditorMode
// (prioriteit 101): eerst de acties, dan de uitleg.
add_action( 'admin_bar_menu', array( $this, 'rapport_node' ), 102 );
// De vaste opmaak van het paneel gaat mee met de stylesheet van de
// adminbalk zelf, via de enqueue-keten. De kleur van het stipje kan
// daar niet bij — die is pas na het renderen bekend — en wordt door
// finalize() als attribuut gezet, in dezelfde ronde als het rapport.
add_action( 'wp_enqueue_scripts', array( $this, 'styles' ), 20 );
// De invulronde: de paginascan geeft aan het einde van het request de
// complete pagina door dit filter, en dan pas bestaan alle tellingen.
add_filter( 'yinga_focus/page_output', array( $this, 'finalize' ) );
}
/**
* De minimale outputbuffer voor de invulronde, alleen als de paginascan
* er niet is om dat werk te doen.
*
* ── Waarom er een buffer nodig is ──────────────────────────────────────
*
* De adminbalk wordt bovenaan de pagina gerenderd, vóór de afbeeldingen.
* Wat er met die afbeeldingen gebeurt, weten we pas als de pagina klaar
* is. Daarom schrijft menu() sleuven en vult finalize() ze aan het einde
* in — en daarvoor moet de hele pagina even door onze handen.
*
* ── Waarom hij expliciet dicht gaat ────────────────────────────────────
*
* Een buffer die open blijft tot PHP hem bij het afsluiten opruimt, werkt
* meestal, maar in een gedeelde omgeving als WordPress is "meestal" niet
* goed genoeg: opent of sluit een ander onderdeel een buffer die niet met
* de onze spoort, dan raakt de stapel scheef. Dus onthouden we op welk
* niveau we openden en sluiten we precies die buffer zelf, op `shutdown`,
* vóór WordPress zijn eigen opruimronde (wp_ob_end_flush_all, prioriteit 1)
* draait.
*
* De nettere route — de template-outputbuffer die WordPress 6.9 biedt —
* kan hier nog niet: de plugin ondersteunt 6.5. Zodra het minimum omhoog
* gaat, hoort dit daarheen.
*/
public function maybe_buffer(): void {
if ( ! $this->showing() || $this->buffer_level > 0 ) {
return;
}
$started = ob_start(
static function ( string $html ): string {
try {
return (string) apply_filters( 'yinga_focus/page_output', $html );
} catch ( \Throwable $error ) {
unset( $error );
return $html;
}
}
);
if ( ! $started ) {
return;
}
$this->buffer_level = ob_get_level();
add_action( 'shutdown', array( $this, 'close_buffer' ), 0 );
}
/**
* Sluit de buffer die maybe_buffer() opende — en alleen die.
*
* Buffers sluiten van buiten naar binnen, dus eerst gaat alles weg wat
* ná ons is geopend en nog open staat; dat zou PHP bij het afsluiten toch
* doen, alleen nu gebeurt het op een voorspelbaar moment. Ligt onze
* buffer er niet meer (iemand anders heeft hem al gesloten), dan doen we
* niets: een buffer van een ander sluiten is precies de fout die we hier
* willen voorkomen.
*/
public function close_buffer(): void {
if ( $this->buffer_level <= 0 ) {
return;
}
while ( ob_get_level() >= $this->buffer_level ) {
if ( ! ob_end_flush() ) {
break;
}
}
$this->buffer_level = 0;
}
/**
* Zet het journaal aan zodra er iemand meekijkt die er iets aan heeft.
*
* Dit gebeurt vóór het renderen, want daarna is het te laat: de beslissingen
* die we willen vastleggen worden tijdens het renderen genomen.
*/
public function maybe_activate(): void {
if ( $this->showing() ) {
$this->policy->journal()->activate();
}
}
/**
* Mag deze bezoeker het rapport zien?
*
* @return bool
*/
private function showing(): bool {
return ! is_admin()
&& is_user_logged_in()
&& current_user_can( 'upload_files' )
&& is_admin_bar_showing();
}
/**
* Bouwt het menu.
*
* @param mixed $bar De WP_Admin_Bar.
*/
public function menu( $bar = null ): void {
if ( ! is_object( $bar ) || ! method_exists( $bar, 'add_node' ) || ! $this->showing() ) {
return;
}
// Sleuven, geen cijfers: de balk rendert bovenaan de pagina, vóór de
// afbeeldingen. finalize() vult ze aan het einde van het request in.
$bar->add_node(
array(
'id' => 'yinga-focus',
'title' => ''
. '' . esc_html( Naming::product() ) . '',
'href' => admin_url( 'options-media.php#yinga_focus' ),
'meta' => array( 'title' => Naming::product() ),
)
);
// De vergelijkingsknop staat er altijd, en is puur request-afhankelijk —
// die kan dus wél meteen. Twee tabbladen naast elkaar, één met en één
// zonder: het enige echte bewijs.
$this->compare_link( $bar );
}
/**
* Het rapport-item, als laatste regel in het submenu.
*
* @param mixed $bar De WP_Admin_Bar.
*/
public function rapport_node( $bar = null ): void {
if ( ! is_object( $bar ) || ! method_exists( $bar, 'add_node' ) || ! $this->showing() ) {
return;
}
$bar->add_node(
array(
'id' => 'yinga-focus-rapport',
'parent' => 'yinga-focus',
'title' => '' . esc_html__( 'Building report…', 'yinga-focus' ) . '',
)
);
}
/**
* Vult de sleuven in de al gerenderde adminbalk met het echte rapport.
*
* Draait aan het einde van het request, in de outputbuffer van de
* paginascan, wanneer alle routes hun werk hebben gedaan en het journaal
* compleet is.
*
* @param mixed $html De complete pagina.
* @return mixed
*/
public function finalize( $html ) {
if ( ! is_string( $html ) || ! $this->showing() ) {
return $html;
}
if ( false === strpos( $html, 'data-yf-slot=' ) ) {
return $html;
}
$totals = $this->policy->journal()->totals();
// Eerst het stipje: een style-attribuut op de sleuf, geen