Skip to content
Snippets Groups Projects
StudipLog.class.php 13.5 KiB
Newer Older
<?php
/**
 * StudipLog
 * Internal API for the Stud.IP logging functions.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License as
 * published by the Free Software Foundation; either version 2 of
 * the License, or (at your option) any later version.
 *
 * @author      Peter Thienel <thienel@data-quest.de>
 * @copyright   2013 Stud.IP Core-Group
 * @license     http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
 * @category    Stud.IP
 * @since       3.0
 */

class StudipLog
{
    /**
     * Magic log, intercepts all undefined static method calls
     * called method name must be the name of a log action
     *
     * @param string $name
     * @param array $arguments
     * @return boolean True if event was written or false if logging is disabled
     */
    public static function __callStatic($name, $arguments)
    {
        $log_action_name = mb_strtoupper($name);
        $log_action = LogAction::findByName($log_action_name);
        if ($log_action) {
            return call_user_func_array('StudipLog::log', array_merge([$log_action_name], $arguments));
        }
        throw new BadMethodCallException('Unknown method called: '
                . $log_action_name);
    }

    /**
    * Logs an event to the database after a certain action took place along with
    * the ids of the range object the action possibly affected. You can provide
    * additional info as well as debug information.
    *
    * @param String $action_name     Name of the action that took place
    * @param mixed  $affected   Range id that was affected by the action, if any
    * @param mixed  $coaffected Range id that was possibly affected as well
    * @param mixed  $info       Information to add to the event
    * @param mixed  $dbg_info   Debug information to add to the event
    * @param mixed  $user_id    Provide null for the current user id
    **/
    public static function log(
        $action_name,
        $affected = null,
        $coaffected = null,
        $info = null,
        $dbg_info = null,
        $user_id = null
    ) {
        if (!Config::get()->LOG_ENABLE) {
            return false;
        }

        // automagically set current user as agent
        if (!$user_id) {
            $user_id = $GLOBALS['user']->id;
        }

        $log_action = LogAction::findOneByName($action_name);
        if (!$log_action) {
            // Action doesn't exist -> LOG_ERROR
            $debug = sprintf(
                'StudipLog::log(%s,%s,%s,%s,%s) for user %s',
                $action_name,
                $affected,
                $coaffected,
                $info,
                $dbg_info,
                $user_id
            );
            self::log('LOG_ERROR', null, null, null, $debug);
            return false;
        }

        if (!$log_action->isActive()) {
            return false;

        $log_event = new LogEvent();
        $log_event->user_id = $user_id;
        $log_event->action_id = $log_action->getId();
        $log_event->affected_range_id = $affected;
        $log_event->coaffected_range_id = $coaffected;
        $log_event->info = $info;
        $log_event->dbg_info = $dbg_info;
        $log_event->store();
        return true;
    }

    /**
     * Registers a new log action in database.
     * Use this function to register log actions for Stud.IP core objects.
     *
     * @param string $name The name of the action.
     * @param string $description The action's description.
     * @param string $info_template The template
     * @param string $class Name of the core class.
     */
    public static function registerAction($name, $description, $info_template,
            $class)
    {
        $action = new LogAction();
        $action->name = $name;
        $action->description = $description;
        $action->info_template = $info_template;
        $action->class = $class;
        $action->type = 'core';
        $action->store();
    }

    /**
     * Registers a new log action in database.
     * Use this function to register log actions for plugin classes.
     *
     * @param string $name The name of the action.
     * @param string $description The action's description.
     * @param string $info_template The template
     * @param string $plugin_class_name Name of the plugin class.
     */
    public static function registerActionPlugin($name, $description,
            $info_template, $plugin_class_name)
    {
        $action = new LogAction();
        $action->name = $name;
        $action->description = $description;
        $action->info_template = $info_template;
        $action->class = $plugin_class_name;
        $action->type = 'plugin';
        $action->store();
    }

    /**
     * Registers a new log action in database.
     * Use this function to register log actions for arbitrary objects.
     *
     * @param string $name The name of the action.
     * @param string $description The action's description.
     * @param string $info_template The template
     * @param string $filename Path to the file with the class (relative
     * to Stud.IP root).
     * @param string $class Name of class to be logged.
     */
    public static function registerActionFile($name, $description,
            $info_template, $filename, $class)
    {
        $path_file = $GLOBALS['STUDIP_BASE_PATH'] . '/' . $filename;
        if (!file_exists($path_file)) {
            $message = sprintf('Task class file "%s" does not exist.',
                    $path_file);
            throw new InvalidArgumentException($message);
        }
        $action = new LogAction();
        $action->name = $name;
        $action->description = $description;
        $action->info_template = $info_template;
        $action->filename = $path_file;
        $action->class = $class;
        $action->type = 'file';
        $action->store();
    }

    /**
     * Removes the action from database.
     * Deletes all related log events also.
     *
     * @param string $name The name of the log action.
     * @return mixed Number of deleted objects or false if action is unknown.
     */
    public static function unregisterAction($name)
    {
        $action = LogAction::findOneByName($name);
        if ($action) {
            return $action->delete();
        }
        return false;
    }

    /**
     * Finds all seminars by given search string. Searches for the name of
     * existing or already deleted seminars.
     *
     * @param string $needle The needle to search for.
     * @return array
     */
    public static function searchSeminar($needle)
    {
        $result = [];

        // search for active seminars
        $courses = Course::findBySQL("VeranstaltungsNummer LIKE CONCAT('%', :needle, '%')
                     OR seminare.Name LIKE CONCAT('%', :needle, '%') ORDER BY start_time DESC",
                [':needle' => $needle]);

        foreach ($courses as $course) {
            $title = sprintf('%s %s (%s)',
                             $course->VeranstaltungsNummer,
                             my_substr($course->name, 0, 40),
                             $course->start_semester->name);
                $result[] = [$course->getId(), $title];
        }

        // search deleted seminars
        // SemName and Number is part of info field, old id (still in DB) is in affected column
        $log_action_ids_archived_seminar = SimpleORMapCollection::createFromArray(
                LogAction::findBySQL(
                    "name IN ('SEM_ARCHIVE', 'SEM_DELETE_FROM_ARCHIVE')"))
                ->pluck('action_id');
        $log_events_archived_seminar = LogEvent::findBySQL("info LIKE CONCAT('%', ?, '%')
                AND action_id IN (?) ",
                [$needle, $log_action_ids_archived_seminar]);
        foreach ($log_events_archived_seminar as $log_event) {
            $title = sprintf('%s (%s)', my_substr($log_event->info, 0, 40), _('gelöscht'));
            $result[] = [$log_event->affected_range_id, $title];
        }

        return $result;
    }

    /**
     * Finds all institutes by given search string. Searches for the name of
     * existing or already deleted institutes.
     *
     * @param type $needle The needle to search for.
     * @return array
     */
    public static function searchInstitute($needle)
    {
        $result = [];

        $institutes = Institute::findBySQL(
                "name LIKE CONCAT('%', ?, '%') ORDER BY name", [$needle]);
        foreach ($institutes as $institute) {
            $result[] = [$institute->getId(), my_substr($institute->name, 0, 28)];
        }

        // search for deleted institutes
        // Name of deleted institute is part of info field,
        // old id (still in DB) is in affected column
        $log_action_delete_institute = LogAction::findOneByName('INST_DEL');
        $log_events_delete_institute = LogEvent::findBySQL(
                "action_id = ? AND info LIKE CONCAT('%', ?, '%')",
                [$log_action_delete_institute->getId(), $needle]);
        foreach ($log_events_delete_institute as $log_event) {
            $title = sprintf('%s (%s)', $log_event->info, _('gelöscht'));
            $result[] = [$log_event->affected_range_id, $title];
        }

        return $result;
    }

    /**
     * Finds all users by given search string. Searches for the users id,
     * part of the name or the username.
     *
     * @param type $needle The needle to search for.
     * @return array
     */
    public static function searchUser($needle)
    {
        $users = User::findBySQL(
            "Nachname LIKE CONCAT('%', :needle, '%')
             OR Vorname LIKE CONCAT('%', :needle, '%')
             OR CONCAT(Nachname, ', ', Vorname) LIKE CONCAT('%', :needle, '%')
             OR CONCAT(Vorname, ' ', Nachname) LIKE CONCAT('%', :needle, '%')
             OR username LIKE CONCAT('%', :needle, '%')
             ORDER BY Nachname DESC, Vorname DESC",
            [':needle' => $needle]
        );

        $result = [];
        foreach ($users as $user) {
            $name = sprintf(
                '%s (%s)',
                my_substr($user->getFullname(), 0, 20),
                $user->username
            );
            $result[] = [$user->getId(), $name];
        }

        // search for deleted users
        //
        // The name of the user is part of info field,
        // old id (still in DB) is in affected column.
        //
        // The log action "USER_DEL" was removed from the list of initially
        // registered log actions in the past.
        // Search for the user if it is still in database. If not, the search
        // for deleted users is not possible.
        $log_action_deleted_user = LogAction::findOneByName('USER_DEL');
        if ($log_action_deleted_user) {
            $log_events_deleted_user = LogEvent::findBySQL(
                "action_id = ? AND info LIKE CONCAT('%', ?, '%')",
                [$log_action_deleted_user->getId(), $needle]
            );
            foreach ($log_events_deleted_user as $log_event) {
                $name = sprintf('%s (%s)', $log_event->info, _('gelöscht'));
                $result[] = [$log_event->affected_range_id, $name];
            }
        }

        return $result;
    }

    /**
     * Finds all resources by given search string. The search string can be
     * either a resource id or part of the name.
     *
     * @param string $needle The needle to search for.
     * @return array
     */
    public static function searchResource($needle)
    {
        $result = [];

        $query = "SELECT id, name FROM resources WHERE name LIKE CONCAT('%', ?, '%') ORDER by name";
        $statement = DBManager::get()->prepare($query);
        $statement->execute([$needle]);

        while ($row = $statement->fetch(PDO::FETCH_ASSOC)) {
            $result[] = [$row['id'], my_substr($row['name'], 0, 30)];
        }

        return $result;
    }

    /**
     * Finds all objects related to the given action by search string.
     * The search string can be either a part of the name or the id
     * of the object.
     *
     * Calls the method Loggable::logSearch() to retrieve the result.
     *
     * @param string $needle
     * @param type $action_id
     * @return type
     */
    public static function searchObjectByAction($needle, $action_id)
    {
        $action = LogAction::find($action_id);

        if ($action) {
            switch ($action->type) {
                case 'plugin':
                    $plugin_manager = PluginManager::getInstance();
                    $plugin_info = $plugin_manager->getPluginInfo($action->class);
                    $class_name = $plugin_info['class'];
                    $plugin = $plugin_manager->getPlugin($class_name);
                    if ($plugin instanceof Loggable) {
                        return $class_name::logSearch($needle, $action->name);
                    }
                    break;
                case 'file':
                    if (!file_exists($action->filename)) {
                        require_once($action->filename);
                        $class_name = $action->class;
                        if ($class_name instanceof Loggable) {
                            return $class_name::logSearch($needle, $action->name);
                        }
                    }
                    break;
                case 'core':
                    $class_name = $action->class;
                    $interfaces = class_implements($class_name);
                    if (isset($interfaces['Loggable'])) {
                        return $class_name::logSearch($needle, $action->name);
                    }
            }
        }
        return [];
    }
}