PHP function to check whether a user is logged in
One of the problems you're facing is the pyramid of doom - deeply nested control structures that make for hard to read code.

You can simplify your code by inverting the conditions in your if-statements. For example, instead of

if(isset($_COOKIE['authToken'])) {
    // do something with authToken
    // ...
    // (potentially) lots of code
    // ...         
} else {
    // throw an error / return
}


you could write

if(!isset($_COOKIE['authToken'])) {
    return;
}

// do something with authToken
// ...


By applying this to your the isLoggedIn function, you get a flatter, more easily readable equivalent:

public static function isLoggedIn(){
    Session::openSession();

    if(isset($_SESSION['userId'])){
        return true;
    }

    if(!isset($_COOKIE['authToken'])){
      return self::notLogged();
    }

    list($selector, $token) = explode(':', $_COOKIE['authToken']);
    $result = Database::query('SELECT * FROM logins WHERE login_selector = ?', array($selector))[0];    

    if(!$result) {
      return self::notLogged(); 
    }

    if(hash_equals($result['login_token'], hash('sha256', base64_decode($token)))){
      self::relogUser($result['login_userId']);
      return true;        
    }

    return self::notLogged();    
}


Also, the method naming seems a little off. The name isLoggedIn implies that the function just checks if a user is logged in or not and nothing else. What the method actually does is to check if a user logged in and if not, try to log them in. If that fails, it calls the method self::notLogged.

The next step might be to tear apart the function into smaller functions that actually tell you what they do from their name alone - this will avoid confusion. See Single responsibility principle - this applies to functions as well - a function should ideally not do more than one thing.

In particular, the part of your code that performs the authentication and/or login-checking should not be concerned with what happens when authentication has succeeded/failed. Instead, they should signal their respective results to the calling function, either by returning a value or by throwing an Exception.

Exceptions have the added benefit of explicitly signaling to developers working with the code that a function has somehow failed to do what it was meant to. Better yet, they give you the freedom to express what exactly went wrong and how.

All in all, you might end up with something like this:

public static function authenticate() {
    if (self::isLoggedIn()) {
        // user is logged in already - all done!
        return;
    }

    // try to login user using authToken
    if(!isset($_COOKIE['authToken'])){
        throw new \Exception('Authentication failed! No authToken provided');
    }

    list($selector, $token) = explode(':', $_COOKIE['authToken']);
    $result = Database::query('SELECT * FROM logins WHERE login_selector = ?', array($selector))[0];    

    if(!$result) {
        throw new \Exception('Authentication failed! Invalid authToken: login not found');
    }

    if(!hash_equals($result['login_token'], hash('sha256', base64_decode($token)))) {
        throw new \Exception('Authentication failed! Invalid authToken: key mismatch');
    } 

    self::relogUser($result['login_userId']);
}

public static function isLoggedIn(){
    Session::openSession();
    return isset($_SESSION['userId']);
}

public static function handleAuthentication() {
    try {
        self::authenticate();
    } catch (\Exception $e) {
        self::notLogged();
    }
}