1<?php
2
3namespace dokuwiki\Debug;
4
5use dokuwiki\Extension\Event;
6use dokuwiki\Extension\EventHandler;
7use dokuwiki\Logger;
8
9class DebugHelper
10{
11    protected const INFO_DEPRECATION_LOG_EVENT = 'INFO_DEPRECATION_LOG';
12
13    /**
14     * Check if deprecation messages shall be handled
15     *
16     * This is either because its logging is not disabled or a deprecation handler was registered
17     *
18     * @return bool
19     */
20    public static function isEnabled()
21    {
22        /** @var EventHandler $EVENT_HANDLER */
23        global $EVENT_HANDLER;
24        if (
25            !Logger::getInstance(Logger::LOG_DEPRECATED)->isLogging() &&
26            (!$EVENT_HANDLER instanceof EventHandler || !$EVENT_HANDLER->hasHandlerForEvent('INFO_DEPRECATION_LOG'))
27        ) {
28            // avoid any work if no one cares
29            return false;
30        }
31        return true;
32    }
33
34    /**
35     * Log accesses to deprecated fucntions to the debug log
36     *
37     * @param string $alternative (optional) The function or method that should be used instead
38     * @param int $callerOffset (optional) How far the deprecated method is removed from this one
39     * @param string $thing (optional) The deprecated thing, defaults to the calling method
40     * @triggers \dokuwiki\Debug::INFO_DEPRECATION_LOG_EVENT
41     */
42    public static function dbgDeprecatedFunction($alternative = '', $callerOffset = 1, $thing = '')
43    {
44        if (!self::isEnabled()) return;
45
46        $backtrace = debug_backtrace();
47        for ($i = 0; $i < $callerOffset; ++$i) {
48            if (count($backtrace) > 1) array_shift($backtrace);
49        }
50
51        [$self, $call] = $backtrace;
52
53        self::triggerDeprecationEvent(
54            $backtrace,
55            $alternative,
56            self::formatCall($self),
57            self::formatCall($call),
58            $self['file'] ?? $call['file'] ?? '',
59            $self['line'] ?? $call['line'] ?? 0
60        );
61    }
62
63    /**
64     * Format the given backtrace info into a proper function/method call string
65     * @param array $call
66     * @return string
67     */
68    protected static function formatCall($call)
69    {
70        $thing = '';
71        if (!empty($call['class'])) {
72            $thing .= $call['class'] . '::';
73        }
74        $thing .= $call['function'] . '()';
75        return trim($thing, ':');
76    }
77
78    /**
79     * This marks logs a deprecation warning for a property that should no longer be used
80     *
81     * This is usually called withing a magic getter or setter.
82     * For logging deprecated functions or methods see dbgDeprecatedFunction()
83     *
84     * @param string $class The class with the deprecated property
85     * @param string $propertyName The name of the deprecated property
86     *
87     * @triggers \dokuwiki\Debug::INFO_DEPRECATION_LOG_EVENT
88     */
89    public static function dbgDeprecatedProperty($class, $propertyName)
90    {
91        if (!self::isEnabled()) return;
92
93        $backtrace = debug_backtrace();
94        array_shift($backtrace);
95        $call = $backtrace[1];
96        $caller = trim($call['class'] . '::' . $call['function'] . '()', ':');
97        $qualifiedName = $class . '::$' . $propertyName;
98        self::triggerDeprecationEvent(
99            $backtrace,
100            '',
101            $qualifiedName,
102            $caller,
103            $backtrace[0]['file'],
104            $backtrace[0]['line']
105        );
106    }
107
108    /**
109     * Trigger a custom deprecation event
110     *
111     * Usually dbgDeprecatedFunction() or dbgDeprecatedProperty() should be used instead.
112     * This method is intended only for those situation where they are not applicable.
113     *
114     * @param string $alternative
115     * @param string $deprecatedThing
116     * @param string $caller
117     * @param string $file
118     * @param int $line
119     * @param int $callerOffset How many lines should be removed from the beginning of the backtrace
120     */
121    public static function dbgCustomDeprecationEvent(
122        $alternative,
123        $deprecatedThing,
124        $caller,
125        $file,
126        $line,
127        $callerOffset = 1
128    ) {
129        if (!self::isEnabled()) return;
130
131        $backtrace = array_slice(debug_backtrace(), $callerOffset);
132
133        self::triggerDeprecationEvent(
134            $backtrace,
135            $alternative,
136            $deprecatedThing,
137            $caller,
138            $file,
139            $line
140        );
141    }
142
143    /**
144     * @param array $backtrace
145     * @param string $alternative
146     * @param string $deprecatedThing
147     * @param string $caller
148     * @param string $file
149     * @param int $line
150     */
151    private static function triggerDeprecationEvent(
152        array $backtrace,
153        $alternative,
154        $deprecatedThing,
155        $caller,
156        $file,
157        $line
158    ) {
159        $data = [
160            'trace' => $backtrace,
161            'alternative' => $alternative,
162            'called' => $deprecatedThing,
163            'caller' => $caller,
164            'file' => $file,
165            'line' => $line,
166        ];
167        $event = new Event(self::INFO_DEPRECATION_LOG_EVENT, $data);
168        if ($event->advise_before()) {
169            $msg = $event->data['called'] . ' is deprecated. It was called from ';
170            $msg .= $event->data['caller'] . ' in ' . $event->data['file'] . ':' . $event->data['line'];
171            if ($event->data['alternative']) {
172                $msg .= ' ' . $event->data['alternative'] . ' should be used instead!';
173            }
174            Logger::getInstance(Logger::LOG_DEPRECATED)->log($msg);
175        }
176        $event->advise_after();
177    }
178}
179