*/ public function app(): array { return array( 'order' => 60, 'category' => 'activities', 'core' => false, 'deps' => array( ), 'default' => true, 'label_key' => 'app_stream', 'desc_key' => 'app_stream_desc', 'icon' => 'stream', 'cron' => array( ), ); } /** * @return array{label: string, desc: string} */ public function labels(): array { return array( 'label' => __( 'Stream', 'plugixa-crm' ), 'desc' => __( 'The activity feed on every record, and following a record’s changes.', 'plugixa-crm' ), ); } public function migrate(): void { Schema::install(); } /** * @return array */ public function capabilities(): array { // Reading a stream is reading the record it belongs to, and the routes authorise against // exactly that. A capability of its own would be a second answer to a settled question. return array(); } public function registerServices( Container $container ): void { unset( $container ); } public function registerRoutes( Container $container ): void { $container->get( StreamController::class )->register_routes(); } public function registerHooks( Container $container ): void { add_filter( 'plugixa_crm_admin_i18n_strings', static fn( array $strings ): array => array_merge( $strings, Strings::all() ) ); $recorder = $container->get( StreamRecorder::class ); add_action( 'plugixa_crm_entity_created', array( $recorder, 'created' ), 10, 3 ); add_action( 'plugixa_crm_entity_updated', array( $recorder, 'updated' ), 10, 4 ); add_action( 'plugixa_crm_entity_assigned', array( $recorder, 'assigned' ), 10, 4 ); add_action( 'plugixa_crm_entity_deleted', array( $recorder, 'deleted' ), 10, 2 ); add_action( 'plugixa_crm_entity_restored', array( $recorder, 'restored' ), 10, 2 ); /** * Is every status change of this kind of record written down, as a `status` note? * * The question a dashboard trend has to ask before it walks a count back through the notes * table: without the answer "yes", the notes are an incomplete history and the walk would * draw a confident, wrong line. Answered HERE because only this module knows — it is the one * writing them — and a module that asks must not name it. When Stream is not running this * filter has no listener, the answer stays false, and the trend is simply not drawn. */ add_filter( 'plugixa_crm_status_history_recorded', static function ( bool $recorded, string $entity_type ) use ( $container ): bool { return $recorded || ( $container->get( StreamService::class )->streams( $entity_type ) && null !== StreamType::status_field( $entity_type ) ); }, 10, 2 ); /** * The dashboard's "Recent activity" widget. * * A BRANCH ON `dashboard/stats` RATHER THAN A ROUTE OF ITS OWN. Every widget on that screen * shares one query key, so six widgets cost one request; a private endpoint would add a * second round trip to every dashboard load for five rows. The branch is absent — and the * widget therefore renders nothing — on any build without this module. */ add_filter( 'plugixa_crm_dashboard_stats', static function ( array $stats ) use ( $container ): array { $stats['stream'] = array( 'recent' => $container->get( Services\StreamService::class )->recent( 6 ), ); return $stats; } ); } /** * What must keep listening while this app is switched off — its half of purge, merge, erasure * and uninstall. See LifecycleInterface. * * @param Container $container The container. */ public function registerLifecycle( Container $container ): void { // A purged record must not leave followers behind: auto_increment reissues ids, and an // orphaned subscription would silently make somebody watch a record they never chose. The // entries themselves go with the Notes module's own purge listener. add_action( 'plugixa_crm_before_purge', static function ( string $entity_type, int $id ) use ( $container ): void { $container->get( SubscriptionRepository::class )->purge_for_record( $entity_type, $id ); }, 10, 2 ); } }