Skip to content
Snippets Groups Projects
Select Git revision
  • f6828d155b6b49a6e92b3b728d288ac8cabf7737
  • main default protected
  • studip-rector
  • ci-opt
  • course-members-export-as-word
  • data-vue-app
  • pipeline-improvements
  • webpack-optimizations
  • rector
  • icon-renewal
  • http-client-and-factories
  • jsonapi-atomic-operations
  • vueify-messages
  • tic-2341
  • 135-translatable-study-areas
  • extensible-sorm-action-parameters
  • sorm-configuration-trait
  • jsonapi-mvv-routes
  • docblocks-for-magic-methods
19 results

DBSchemaVersion.php

Blame
  • Forked from Stud.IP / Stud.IP
    Source project has a limited visibility.
    Code owners
    Assign users and groups as approvers for specific file changes. Learn more.
    CronjobSchedule.class.php 10.77 KiB
    <?php
    // +---------------------------------------------------------------------------+
    // This file is part of Stud.IP
    // CronjobSchedule.class.php
    //
    // Copyright (C) 2013 Jan-Hendrik Willms <tleilax+studip@gmail.com>
    // +---------------------------------------------------------------------------+
    // 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 any later version.
    // +---------------------------------------------------------------------------+
    // This program is distributed in the hope that it will be useful,
    // but WITHOUT ANY WARRANTY; without even the implied warranty of
    // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    // GNU General Public License for more details.
    // You should have received a copy of the GNU General Public License
    // along with this program; if not, write to the Free Software
    // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
    // +---------------------------------------------------------------------------+
    
    /**
     * CronjobSchedule - Model for the database table "cronjobs_schedules"
     *
     * @author      Jan-Hendrik Willms <tleilax+studip@gmail.com>
     * @license     http://www.gnu.org/licenses/gpl-2.0.html GPL version 2
     * @category    Stud.IP
     * @since       2.4
     *
     * @property string schedule_id database column
     * @property string id alias column for schedule_id
     * @property string task_id database column
     * @property string active database column
     * @property string title database column
     * @property string description database column
     * @property string parameters database column
     * @property string priority database column
     * @property string type database column
     * @property string minute database column
     * @property string hour database column
     * @property string day database column
     * @property string month database column
     * @property string day_of_week database column
     * @property string next_execution database column
     * @property string last_execution database column
     * @property string last_result database column
     * @property string execution_count database column
     * @property string mkdate database column
     * @property string chdate database column
     * @property SimpleORMapCollection logs has_many CronjobLog
     * @property CronjobTask task belongs_to CronjobTask
     */
    
    class CronjobSchedule extends SimpleORMap
    {
        const PRIORITY_LOW    = 'low';
        const PRIORITY_NORMAL = 'normal';
        const PRIORITY_HIGH   = 'high';
    
        protected static function configure($config = [])
        {
            $config['db_table'] = 'cronjobs_schedules';
    
            $config['belongs_to']['task'] = [
                'class_name'  => CronjobTask::class,
                'foreign_key' => 'task_id',
            ];
            $config['has_many']['logs'] = [
                'class_name' => CronjobLog::class,
                'on_delete'  => 'delete',
                'on_store'   => 'store',
            ];
    
            $config['registered_callbacks']['before_store'][]     = 'cbJsonifyParameters';
            $config['registered_callbacks']['after_store'][]      = 'cbJsonifyParameters';
            $config['registered_callbacks']['after_initialize'][] = 'cbJsonifyParameters';
    
            parent::configure($config);
        }
    
        /**
         * Returns a mapped version of the priorities (key = priority value,
         * value = localized priority label).
         *
         * @return Array The mapped priorities
         */
        public static function getPriorities()
        {
            $mapping = [];
            $mapping[self::PRIORITY_LOW]    = _('niedrig');
            $mapping[self::PRIORITY_NORMAL] = _('normal');
            $mapping[self::PRIORITY_HIGH]   = _('hoch');
    
            return $mapping;
        }
    
        /**
         * Maps a priority value to it's localized label.
         *
         * @param  String $priority Priority value
         * @return String The localized label
         * @throws RuntimeException when an unknown priority value is passed
         */
        public static function describePriority($priority)
        {
            $priority = $priority ?? 'normal';
    
            $mapping = self::getPriorities();
            if (!isset($mapping[$priority])) {
                throw new RuntimeException('Access to unknown priority "' . $priority . '"');
            }
    
            return $mapping[$priority];
        }
    
        /**
         * replaces title with task name if title is empty.
         *
         * @return string the title or the task name
         */
        public function getTitle()
        {
            return ($this->content['title'] ?: $this->task->name) ?? '';
        }
    
        protected function cbJsonifyParameters($type)
        {
            if ($type === 'before_store' && !is_string($this->parameters)) {
                $this->parameters = json_encode($this->parameters ?: null);
            }
            if (in_array($type, ['after_initialize', 'after_store']) && is_string($this->parameters)) {
                $parameters = json_decode($this->parameters, true) ?: [];
                if ($this->task->valid) {
                    $default_parameters = $this->task->extractDefaultParameters();
                    foreach ($default_parameters as $key => $value) {
                        if (!isset($parameters[$key])) {
                            $parameters[$key] = $value;
                        }
                    }
                }
                $this->parameters = $parameters;
            }
        }
    
        /**
         * Stores the schedule in database. Will bail out with an exception if
         * the provided task does not exists. Will also nullify the title if it
         * matches the task name (see CronjobSchedule::getTitle()).
         *
         * @return CronjobSchedule Returns itself to allow chaining
         */
        public function store()
        {
            if ($this->task === null) {
                $message = sprintf('A task with the id "%s" does not exist.', $this->task_id);
                throw new InvalidArgumentException($message);
            }
    
            // Remove title if it is the default (task's name)
            if ($this->title === $this->task->name) {
                $this->title = null;
            }
    
            parent::store();
    
            return $this;
        }
    
        /**
         * Activates this schedule.
         *
         * @return CronjobSchedule Returns itself to allow chaining
         */
        public function activate()
        {
            $this->active         = 1;
            $this->next_execution = $this->calculateNextExecution();
            $this->store();
    
            return $this;
        }
    
        /**
         * Deactivates this schedule.
         *
         * @return CronjobSchedule Returns itself to allow chaining
         */
        public function deactivate()
        {
            $this->active = 0;
            $this->store();
    
            return $this;
        }
    
        /**
         * Executes this schedule.
         *
         * @param bool $force Pass true to force execution of  the schedule even
         *                    if it's not activated
         * @return mixed The result of the execution
         * @throws RuntimeException When either the schedule or the according is
         *                          not activated
         */
        public function execute($force = false)
        {
            if (!$force && !$this->active) {
                throw new RuntimeException('Execution aborted. Schedule is not active');
            }
            if (!$this->task->active) {
                throw new RuntimeException('Execution aborted. Associated task is not active');
            }
    
            $this->last_execution   = time();
            $this->execution_count += 1;
            $this->next_execution   = $this->calculateNextExecution();
            $this->store();
    
            $this->task->execution_count += 1;
            $this->task->store();
    
            $result = $this->task->engage($this->last_result, $this->parameters);
    
            if ($this->type === 'once') {
                $this->active = 0;
            }
            $this->last_result = $result;
            $this->store();
    
            return $result;
        }
    
        /**
         * Determines whether the schedule should execute given the provided
         * timestamp.
         *
         * @param mixed $now Defines the temporal fix point
         * @return bool Whether the schedule should execute or not.
         */
        public function shouldExecute($now = null)
        {
            return ($now ?: time()) >= $this->next_execution;
        }
    
        /**
         * Calculates the next execution for this schedule.
         *
         * For schedules of type 'once' the check solely tests whether the
         * timestamp has already passed and will return false in that case.
         * Otherwise the defined timestamp will be returned.
         *
         * For schedules of type 'periodic' the next execution
         * is calculated by increasing the current timestamp and testing
         * whether all conditions match. This is not the best method to test
         * and should be optimized sooner or later.
         *
         * @param mixed $now Defines the temporal fix point
         * @return int Timestamp of calculated next execution
         * @throws RuntimeException When calculation takes too long (you should
         *                          check the conditions for validity in that case)
         */
        public function calculateNextExecution($now = null)
        {
            $now = $now ?: time();
    
            if ($this->type === 'once') {
                return $now <= $this->next_execution
                    ? $this->next_execution
                    : false;
            }
    
            $result  = $now;
            $result -= $result % 60;
    
            $i = 366 * 24 * 60; // Maximum: A year
            $offset = 60;
    
            do {
                $result += $offset;
    
                // TODO: Performance - Adjust result according to conditions
                // See http://coderzone.org/library/PHP-PHP-Cron-Parser-Class_1084.htm
                $valid  = $this->testTimestamp($result, $this->minute, 'i')
                       && $this->testTimestamp($result, $this->hour, 'H')
                       && $this->testTimestamp($result, $this->day, 'd')
                       && $this->testTimestamp($result, $this->month, 'm')
                       && $this->testTimestamp($result, $this->day_of_week, 'N');
    
            } while (!$valid && $i-- > 0);
    
            if ($i <= 0) {
                throw new RuntimeException('No result, current: ' . date('d.m.Y H:i', $result));
            }
    
            $this->next_execution = $result;
            return $result;
        }
    
        /**
         * Tests a timestamp against the passed condition.
         *
         * @param int $timestamp The timestamp to test
         * @param mixed $condition Can be either null for "don't care", a positive
         *                         number for an exact moment or a negative number
         *                         for a repeating moment
         * @param String $format Format for date() to extract a portion of the
         *                       timestamp
         */
        protected function testTimestamp($timestamp, $condition, $format)
        {
            if ($condition === null) {
                return true;
            }
    
            $probe     = (int) date($format, $timestamp);
            $condition = (int) $condition;
    
            if ($condition < 0) {
                return ($probe % abs($condition)) === 0;
            }
    
            return $probe === $condition;
        }
    }