JezK
Edit File: vary.cls.php
<?php /** * Manage the X-LiteSpeed-Vary behavior and vary cookie. * * @since 1.1.3 * @package LiteSpeed */ namespace LiteSpeed; defined( 'WPINC' ) || exit(); /** * Handles detection of user state (guest, logged-in, commenter, etc.) * and builds the X-LiteSpeed-Vary header and vary cookie accordingly. */ class Vary extends Root { /** * Log tag used in debug output. * * @var string */ const LOG_TAG = '🔱'; /** * Vary header name. * * @var string */ const X_HEADER = 'X-LiteSpeed-Vary'; /** * Default vary cookie name (used for logged-in/commenter state). * * @var string */ private static $_vary_name = '_lscache_vary'; /** * Whether Ajax calls are permitted to change the vary cookie. * * @var bool */ private static $_can_change_vary = false; /** * Update the default vary cookie name if site settings require it. * * @since 4.0 * @since 7.0 Moved to after_user_init to allow ESI no-vary no conflict. * @return void */ private function _update_vary_name() { $db_cookie = $this->conf( Base::O_CACHE_LOGIN_COOKIE ); // network aware in v3.0. // If no vary set in rewrite rule. if ( ! isset( $_SERVER['LSCACHE_VARY_COOKIE'] ) ) { if ( $db_cookie ) { // Check for ESI no-vary control. $something_wrong = true; if ( ! empty( $_GET[ ESI::QS_ACTION ] ) && ! empty( $_GET['_control'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended $control_raw = wp_unslash( (string) $_GET['_control'] ); // phpcs:ignore WordPress.Security.NonceVerification.Recommended, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized $control = array_map( 'sanitize_text_field', explode( ',', $control_raw ) ); if ( in_array( 'no-vary', $control, true ) ) { self::debug( 'no-vary control existed, bypass vary_name update' ); $something_wrong = false; self::$_vary_name = $db_cookie; } } if ( defined( 'LITESPEED_CLI' ) || wp_doing_cron() ) { $something_wrong = false; } if ( $something_wrong ) { // Display cookie error msg to admin. if ( is_multisite() ? is_network_admin() : is_admin() ) { Admin_Display::show_error_cookie(); } Control::set_nocache( '❌❌ vary cookie setting error' ); } } return; } // DB setting does not exist – nothing to check. if ( ! $db_cookie ) { return; } // Beyond this point, ensure DB vary is present in $_SERVER env. $server_raw = wp_unslash( (string) $_SERVER['LSCACHE_VARY_COOKIE'] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized $vary_arr = array_map( 'trim', explode( ',', $server_raw ) ); if ( in_array( $db_cookie, $vary_arr, true ) ) { self::$_vary_name = $db_cookie; return; } if ( is_multisite() ? is_network_admin() : is_admin() ) { Admin_Display::show_error_cookie(); } Control::set_nocache( 'vary cookie setting lost error' ); } /** * Run after user init to set up vary/caching for current request. * * @since 4.0 * @return void */ public function after_user_init() { $this->_update_vary_name(); // Logged-in user. if ( Router::is_logged_in() ) { // If not ESI, check cache logged-in user setting. if ( ! $this->cls( 'Router' )->esi_enabled() ) { // Cache logged-in => private cache. if ( $this->conf( Base::O_CACHE_PRIV ) && ! is_admin() ) { add_action( 'wp_logout', __NAMESPACE__ . '\Purge::purge_on_logout' ); $this->cls( 'Control' )->init_cacheable(); Control::set_private( 'logged in user' ); } else { // No cache for logged-in user. Control::set_nocache( 'logged in user' ); } } elseif ( ! is_admin() ) { // ESI is on; can be public cache, but ensure cacheable is initialized. $this->cls( 'Control' )->init_cacheable(); } // Clear login state on logout. add_action( 'clear_auth_cookie', [ $this, 'remove_logged_in' ] ); } else { // Only after vary init we can detect guest mode. $this->_maybe_guest_mode(); // Set vary cookie when user logs in (to avoid guest vary). add_action( 'set_logged_in_cookie', [ $this, 'add_logged_in' ], 10, 4 ); add_action( 'wp_login', __NAMESPACE__ . '\Purge::purge_on_logout' ); $this->cls( 'Control' )->init_cacheable(); // Check login-page cacheable setting — login page doesn't go through main WP logic. add_action( 'login_init', [ $this->cls( 'Tag' ), 'check_login_cacheable' ], 5 ); // Optional lightweight guest vary updater. if ( ! empty( $_GET['litespeed_guest'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended add_action( 'wp_loaded', [ $this, 'update_guest_vary' ], 20 ); } } // Commenter checks. add_filter( 'comments_array', [ $this, 'check_commenter' ] ); // Set vary cookie for commenter. add_action( 'set_comment_cookies', [ $this, 'append_commenter' ] ); // REST: don't change vary because they don't carry on user info usually. add_action( 'rest_api_init', function () { self::debug( 'Rest API init disabled vary change' ); add_filter( 'litespeed_can_change_vary', '__return_false' ); } ); } /** * Mark request as Guest mode when applicable. * * @since 4.0 * @return void */ private function _maybe_guest_mode() { if ( defined( 'LITESPEED_GUEST' ) ) { self::debug( '👒👒 Guest mode ' . ( LITESPEED_GUEST ? 'predefined' : 'turned off' ) ); return; } if ( ! $this->conf( Base::O_GUEST ) ) { return; } // If vary is set, then not a guest. if ( self::has_vary() ) { return; } // Admin QS present? not a guest. if ( ! empty( $_GET[ Router::ACTION ] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended return; } if ( wp_doing_ajax() ) { return; } if ( wp_doing_cron() ) { return; } // Request to update vary? not a guest. if ( ! empty( $_GET['litespeed_guest'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended return; } // User explicitly turned guest off. if ( ! empty( $_GET['litespeed_guest_off'] ) ) { // phpcs:ignore WordPress.Security.NonceVerification.Recommended return; } self::debug( '👒👒 Guest mode' ); ! defined( 'LITESPEED_GUEST' ) && define( 'LITESPEED_GUEST', true ); if ( $this->conf( Base::O_GUEST_OPTM ) ) { ! defined( 'LITESPEED_GUEST_OPTM' ) && define( 'LITESPEED_GUEST_OPTM', true ); } } /** * Update Guest vary * * @since 4.0 * @deprecated 4.1 Use independent lightweight guest.vary.php instead. * @return void */ public function update_guest_vary() { // Must not be cached. ! defined( 'LSCACHE_NO_CACHE' ) && define( 'LSCACHE_NO_CACHE', true ); $_guest = new Lib\Guest(); if ( $_guest->always_guest() || self::has_vary() ) { // If contains vary already, don't reload (avoid loops). ! defined( 'LITESPEED_GUEST' ) && define( 'LITESPEED_GUEST', true ); self::debug( '🤠🤠Guest' ); echo '[]'; exit; } self::debug( 'Will update guest vary in finalize' ); // Return JSON to trigger reload. echo wp_json_encode( [ 'reload' => 'yes' ] ); exit; } /** * Filter callback on `comments_array` to mark commenter state. * * @since 1.0.4 * * @param array $comments The comments to output. * @return array Filtered comments. */ public function check_commenter( $comments ) { /** * Allow bypassing pending comment check for comment plugins. * * @since 2.9.5 */ if ( apply_filters( 'litespeed_vary_check_commenter_pending', true ) ) { $pending = false; foreach ( $comments as $comment ) { if ( ! $comment->comment_approved ) { $pending = true; break; } } // No pending comments => ensure public cache state. if ( ! $pending ) { self::debug( 'No pending comment' ); $this->remove_commenter(); // Remove commenter prefilled info for public cache. foreach ( $_COOKIE as $cookie_name => $cookie_value ) { if ( strlen( $cookie_name ) >= 15 && 0 === strpos( $cookie_name, 'comment_author_' ) ) { unset( $_COOKIE[ $cookie_name ] ); } } return $comments; } } // Pending comments present — set commenter vary. $this->add_commenter(); if ( $this->conf( Base::O_CACHE_COMMENTER ) ) { Control::set_private( 'existing commenter' ); } else { Control::set_nocache( 'existing commenter' ); } return $comments; } /** * Check if default vary has a value * * @since 1.1.3 * * @return false|string Cookie value or false if missing. */ public static function has_vary() { if ( empty( $_COOKIE[ self::$_vary_name ] ) ) { return false; } // Cookie values are not user-displayed; unslash only. return wp_unslash( (string) $_COOKIE[ self::$_vary_name ] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized } /** * Append user status with logged-in. * * @since 1.1.3 * @since 1.6.2 Removed static referral. * * @param string|false $logged_in_cookie The logged-in cookie value. * @param int|false $expire Expiration timestamp. * @param int|false $expiration Unused (WordPress signature). * @param int|false $uid User ID. * @return void */ public function add_logged_in( $logged_in_cookie = false, $expire = false, $expiration = false, $uid = false ) { // phpcs:ignore Generic.CodeAnalysis.UnusedFunctionParameter.FoundAfterLastUsed self::debug( 'add_logged_in' ); // Allow Ajax vary change during login flow. // NOTE: Run before `$this->_update_default_vary()` to make vary changeable self::can_ajax_vary(); // Ensure vary cookie exists/updated. $this->_update_default_vary( $uid, $expire ); } /** * Remove user logged-in status. * * @since 1.1.3 * @since 1.6.2 Removed static referral. * @return void */ public function remove_logged_in() { self::debug( 'remove_logged_in' ); // Allow Ajax vary change during logout flow. self::can_ajax_vary(); // Force update vary to remove login status. $this->_update_default_vary( -1 ); } /** * Allow vary to be changed for Ajax calls. * * @since 2.2.2 * @since 2.6 Changed to static. * @return void */ public static function can_ajax_vary() { self::debug( '_can_change_vary -> true' ); self::$_can_change_vary = true; } /** * Whether we can change the default vary right now. * * @since 1.6.2 * @return bool */ private function can_change_vary() { // Don't change on Ajax unless explicitly allowed (no webp header). if ( Router::is_ajax() && ! self::$_can_change_vary ) { self::debug( 'can_change_vary bypassed due to ajax call' ); return false; } // Allow only GET/POST. // POST request can set vary to fix #820789 login "loop" guest cache issue. if ( isset( $_SERVER['REQUEST_METHOD'] ) && 'GET' !== $_SERVER['REQUEST_METHOD'] && 'POST' !== $_SERVER['REQUEST_METHOD'] ) { self::debug( 'can_change_vary bypassed due to method not get/post' ); return false; } // Disable when crawler is making the request. if ( ! empty( $_SERVER['HTTP_USER_AGENT'] ) && 0 === strpos( wp_unslash( (string) $_SERVER['HTTP_USER_AGENT'] ), Crawler::FAST_USER_AGENT ) // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized ) { self::debug( 'can_change_vary bypassed due to crawler' ); return false; } if ( ! apply_filters( 'litespeed_can_change_vary', true ) ) { self::debug( 'can_change_vary bypassed due to litespeed_can_change_vary hook' ); return false; } return true; } /** * Update default vary cookie (idempotent within a request). * * @since 1.6.2 * @since 1.6.6.1 Guard to ensure single run. * * @param int|false $uid User ID or false. * @param int|false $expire Expiration timestamp (default: +2 days). * @return void */ private function _update_default_vary( $uid = false, $expire = false ) { // Ensure header output only runs once. if ( ! defined( 'LITESPEED_DID_' . __FUNCTION__ ) ) { define( 'LITESPEED_DID_' . __FUNCTION__, true ); } else { self::debug2( '_update_default_vary bypassed due to run already' ); return; } // ESI shouldn't change vary (main page only). if ( defined( 'LSCACHE_IS_ESI' ) && LSCACHE_IS_ESI ) { self::debug2( '_update_default_vary bypassed due to ESI' ); return; } $vary = $this->finalize_default_vary( $uid ); $current_vary = self::has_vary(); if ( $current_vary !== $vary && 'commenter' !== $current_vary && $this->can_change_vary() ) { if ( ! $expire ) { $expire = time() + 2 * DAY_IN_SECONDS; } $this->_cookie( $vary, (int) $expire ); } } /** * Get the current vary cookie name. * * @since 1.9.1 * @return string */ public function get_vary_name() { return self::$_vary_name; } /** * Check if a user role is in a configured vary group. * * @since 1.2.0 * @since 3.0 Moved here from conf.cls. * * @param string $role User role(s), comma-separated. * @return int|string Group ID or 0. */ public function in_vary_group( $role ) { $group = 0; $vary_groups = $this->conf( Base::O_CACHE_VARY_GROUP ); $roles = explode( ',', $role ); $found = array_intersect( $roles, array_keys( (array) $vary_groups ) ); if ( $found ) { $groups = []; foreach ( $found as $curr_role ) { $groups[] = $vary_groups[ $curr_role ]; } $group = implode( ',', array_unique( $groups ) ); } elseif ( in_array( 'administrator', $roles, true ) ) { $group = 99; } if ( $group ) { self::debug2( 'role in vary_group [group] ' . $group ); } return $group; } /** * Finalize default vary cookie value for current user. * NOTE: Login process will also call this because it does not call wp hook as normal page loading. * * @since 1.6.2 * * @param int|false $uid Optional user ID. * @return false|string False for guests when no vary needed, or hashed vary. */ public function finalize_default_vary( $uid = false ) { // Bypass vary for guests where applicable (avoid non-guest filenames for assets). if ( defined( 'LITESPEED_GUEST' ) && LITESPEED_GUEST ) { return false; } $vary = []; if ( $this->conf( Base::O_GUEST ) ) { $vary['guest_mode'] = 1; } if ( ! $uid ) { $uid = get_current_user_id(); } else { self::debug( 'uid: ' . $uid ); } // Get user role/group. $role = Router::get_role( $uid ); if ( $uid > 0 ) { $vary['logged-in'] = 1; if ( $role ) { // Parse role group from settings. $role_group = $this->in_vary_group( $role ); if ( $role_group ) { $vary['role'] = $role_group; } } // Admin bar preference. $pref = get_user_option( 'show_admin_bar_front', $uid ); self::debug2( 'show_admin_bar_front: ' . var_export( $pref, true ) ); // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_var_export $admin_bar = ( false === $pref || 'true' === $pref ); if ( $admin_bar ) { $vary['admin_bar'] = 1; self::debug2( 'admin bar : true' ); } } else { self::debug( 'role id: failed, guest' ); } /** * Filter vary entries before hashing. * * @since 1.6 Added for Role Excludes for optimization cls * @since 1.6.2 Hooked to webp (legacy) * @since 3.0 Used by 3rd hooks too */ $vary = apply_filters( 'litespeed_vary', $vary ); if ( ! $vary ) { return false; } ksort( $vary ); $list = []; foreach ( $vary as $key => $val ) { $list[] = $key . ':' . $val; } $res = implode( ';', $list ); if ( defined( 'LSCWP_LOG' ) ) { return $res; } // Encrypt in production. return md5( $this->conf( Base::HASH ) . $res ); } /** * Get hash of all varies that affect caching (current cookies + default + env). * * @since 4.0 * @return string */ public function finalize_full_varies() { $vary = $this->_finalize_curr_vary_cookies( true ); $vary .= $this->finalize_default_vary( get_current_user_id() ); $vary .= $this->get_env_vary(); return $vary; } /** * Get request environment vary value (from server variables). * * @since 4.0 * @return string|false */ public function get_env_vary() { $env_vary = isset( $_SERVER['LSCACHE_VARY_VALUE'] ) ? wp_unslash( (string) $_SERVER['LSCACHE_VARY_VALUE'] ) : false; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized if ( ! $env_vary ) { $env_vary = isset( $_SERVER['HTTP_X_LSCACHE_VARY_VALUE'] ) ? wp_unslash( (string) $_SERVER['HTTP_X_LSCACHE_VARY_VALUE'] ) : false; // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized } return $env_vary; } /** * Mark current user as commenter (called on comment submit). * * @since 1.1.6 * @return void */ public function append_commenter() { $this->add_commenter( true ); } /** * Add commenter vary (optionally from redirect). * * @since 1.1.3 * * @param bool $from_redirect Whether request is from redirect page. * @return void */ private function add_commenter( $from_redirect = false ) { // If the cookie is lost somehow, set it. if ( 'commenter' !== self::has_vary() ) { self::debug( 'Add commenter' ); // Save commenter status only for current domain path. $this->_cookie( 'commenter', time() + (int) apply_filters( 'comment_cookie_lifetime', 30000000 ), self::_relative_path( $from_redirect ) ); } } /** * Remove commenter vary if set. * * @since 1.1.3 * @return void */ private function remove_commenter() { if ( 'commenter' === self::has_vary() ) { self::debug( 'Remove commenter' ); $this->_cookie( false, false, self::_relative_path() ); } } /** * Generate a relative cookie path from current request. * * @since 1.1.3 * * @param bool $from_redirect When true, uses HTTP_REFERER; otherwise SCRIPT_URL. * @return string|false Path or false. */ private static function _relative_path( $from_redirect = false ) { $path = false; $tag = $from_redirect ? 'HTTP_REFERER' : 'SCRIPT_URL'; if ( ! empty( $_SERVER[ $tag ] ) ) { $parsed = wp_parse_url( wp_unslash( (string) $_SERVER[ $tag ] ) ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized $path = ! empty( $parsed['path'] ) ? $parsed['path'] : false; self::debug( 'Cookie Vary path: ' . ( $path ? $path : 'false' ) ); } return $path; } /** * Build the final X-LiteSpeed-Vary header for current request. * NOTE: Non caccheable page can still set vary ( for logged in process ). * * @since 1.0.13 * * @return string|void Header string or nothing when not needed. */ public function finalize() { // Finalize default vary for non-guest. if ( ! defined( 'LITESPEED_GUEST' ) || ! LITESPEED_GUEST ) { $this->_update_default_vary(); } $tp_cookies = $this->_finalize_curr_vary_cookies(); if ( ! $tp_cookies ) { self::debug2( 'no customized vary' ); return; } self::debug( 'finalized 3rd party cookies', $tp_cookies ); return self::X_HEADER . ': ' . implode( ',', $tp_cookies ); } /** * Get vary cookies (names or values JSON) added for current page. * * @since 1.0.13 * * @param bool $values_json When true, returns JSON array of cookie values; else cookie=name items. * @return array|string|false List of vary cookie items, JSON string, or false when none. */ private function _finalize_curr_vary_cookies( $values_json = false ) { global $post; $cookies = []; // No need to append default vary cookie name. if ( ! empty( $post->post_password ) ) { $postpass_key = 'wp-postpass_' . COOKIEHASH; if ( $this->_get_cookie_val( $postpass_key ) ) { self::debug( 'finalize bypassed due to password protected vary ' ); // If user has password cookie, do not cache & ignore existing vary cookies. Control::set_nocache( 'password protected vary' ); return false; } $cookies[] = $values_json ? $this->_get_cookie_val( $postpass_key ) : $postpass_key; } $cookies = apply_filters( 'litespeed_vary_curr_cookies', $cookies ); if ( $cookies ) { $cookies = array_filter( array_unique( $cookies ) ); self::debug( 'vary cookies changed by filter litespeed_vary_curr_cookies', $cookies ); } if ( ! $cookies ) { return false; } // Format cookie name data or value data. sort( $cookies ); // Maintain stable order for $values_json=true. foreach ( $cookies as $k => $v ) { $cookies[ $k ] = $values_json ? $this->_get_cookie_val( $v ) : 'cookie=' . $v; } return $values_json ? wp_json_encode( $cookies ) : $cookies; } /** * Get a cookie value safely. * * @since 4.0 * * @param string $key Cookie name. * @return false|string Cookie value or false. */ private function _get_cookie_val( $key ) { if ( ! empty( $_COOKIE[ $key ] ) ) { return wp_unslash( (string) $_COOKIE[ $key ] ); // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.InputNotSanitized } return false; } /** * Set or clear the vary cookie. * * If the vary cookie changed, mark page as non-cacheable for this response. * * @since 1.0.4 * * @param int|false $val Cookie value to set, or false to clear. * @param int $expire Expiration timestamp (ignored when $val is false). * @param string $path Cookie path (false to use COOKIEPATH). * @return void */ private function _cookie( $val = false, $expire = 0, $path = false ) { if ( ! $val ) { $expire = 1; } // HTTPS bypass toggle for clients using both HTTP/HTTPS. $is_ssl = $this->conf( Base::O_UTIL_NO_HTTPS_VARY ) ? false : is_ssl(); setcookie( self::$_vary_name, $val, (int) $expire, $path ? $path : COOKIEPATH, COOKIE_DOMAIN, $is_ssl, true ); self::debug( 'set_cookie ---> [k] ' . self::$_vary_name . ' [v] ' . ( false === $val ? 'false' : $val ) . ' [ttl] ' . ( (int) $expire - time() ) ); } }
BanaBridge News
https://validator.w3.org/feed/docs/rss2.html
Court Orders Ex-Weah Protocol Chief Jailed on US$8 Million Bail, Sends 15 Jurors to Prison Over Misconduct
Women Renew Call for War and Economic Crimes Court at Liberia’s Historic Peace Prayer Site
Finance Minister Ngafuan Urges Transparency, Accountability as Liberia Advances ARREST Agenda at UN Retreat
PPCC Donates ICT Equipment to 50 Government Institutions to Expand Electronic Procurement System
Liberia Commissions €100,000 Elemental Analyzer to Strengthen Environmental Research and Food Security
EPA Ends 16 Years of Renting with Purchase of Permanent Headquarters
West African Tax Bodies Deepen Partnership to Boost Revenue Mobilization, Launch Carbon Tax Study
Liberia Hosts Sierra Leone Procurement Delegation for E-GP Reform Study Tour
World Bank Praises Liberia’s Central Bank for Economic Stability and Private Sector Progress
Liberians See Slight Improvement in Corruption Fight, But Graft Still Viewed as Widespread, CENTAL Report Finds
Canada Afrique Care Foundation Set for Official Launch, Expanding Free Mental Health Access Across Canada and Africa
Weah Claims Liberia Has Become a “Narco State” as Opposition Rallies Behind Calls for Independent Probe into US$19 Million Cocaine Bust
EPA Boss Calls for Stronger Student Engagement, Unveils Push for Academic Partnership at UL Climate Symposium
Education Minister Urges Students to Embrace Tax Responsibility at National Student Tax Day
CDC Secretary General Urges U.S. Government to Review Support to Liberia National Police
Liberia Partners with CACFO to Expand Mental Health Services Ahead of Global Launch
Wreh-Toe Appointed Board Chair of CACFO as Foundation Strengthens Mental Health Push
GIABA Opens New Information Center Headquarters in Abidjan
Liberia Wins Fresh Backing as World Bank Group Executive Praises Progress, Pushes Faster Project Delivery
LRA Honors Liberia’s Top Taxpayers as Revenue Collections Hit Historic High
Root Cause in Question as Border Tensions Persist After Conakry Peace Deal
From Enforcement to Innovation: EPA Marks 2025 as a Defining Year for Liberia’s Environment
Weah Declares ‘Road to 2029 Begins Today’ as CDC Marks 22nd Anniversary in Zwedru
500 Documented Governance Failures: Activist Publishes Sweeping End-of-Year Indictment of Boakai Administration
CDC Calls for Probe into Border Crisis, Cites Alleged Mining Activities
Liberia Moves to Strengthen Small Businesses with $672,000 Packaging Solution Initiative
Former Public Works Minister Ruth Coker Collins Defends Zwedru Corridor Strategy, Calls for “Facts Over Rhetoric” in Infrastructure Debate
Power Shake-Up in CDC Diaspora: NEC Dissolves CDC-USA Interim Leadership, Appoints Rev. Muin to Lead U.S. Chapter
Tweah Reemerges, Sparks Fierce National Debate Over Governance and Liberia’s Future
Koijee Slams Tribal Politics and Political Manipulation in Lofa, Accuses Boakai Government
Mali and Burkina Faso Impose Reciprocal Travel Ban on U.S. Citizens
Guinea Election: A Turn Toward Continuity Amid Questions of Democratic Credibility
Liberia’s Revenue Grows Stronger as LRA Exceeds National Target for Second Year
Liberia’s Gender Legal Landscape: Laws in Place, Systems Lag Far Behind
Liberia’s Economy Gains Momentum as CBL Governor Highlights Progress at ECOWAS Meeting
Liberia Positions Itself for Eco Currency Entry as Economy Expands by 5.1%
From Overcrowded Classrooms to Renewal: NPA Mobilizes $1 Million to Transform UL
Ex-Public Works Minister Ruth Coker Collins Slams Ministry’s “Lack of Capacity,” Defends Her Infrastructure Record
CDC Expels Three Senior Members, Citing Alleged Alignment With Government and Undermining Party Ideology
UL to Celebrate 75 Years of Excellence
Saudi Fund Advances Liberia’s Push for Major Infrastructure Development
West African Financial Leaders Convene in Monrovia for ECOWAS Statutory Meetings
Unlocking Liberia’s Potential: Government Launches Major Revenue and Transparency Reforms
Incremental Gains, Enduring Impunity: Liberia’s CPI Improvement Fails the Test of Reform
Boakai’s Call for Unity Faces Scrutiny as Opposition, Citizens Question Actions Behind the Words
REMAPSEN Liberia Set to Strengthen Health and Environmental Reporting
Energy Sector Women Step Into the Spotlight at Liberia’s International Women’s Day Celebration
FeJAL Concludes Three-Day Retreat, Honors Outstanding Women Journalists
Dr. Rasha Kelej Named Among Africa’s 100 Most Influential Leaders
LAVA Honored for Strengthening Liberia–U.S. Ties at 2025 Military Ball