Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
77.50% covered (warning)
77.50%
31 / 40
20.00% covered (danger)
20.00%
1 / 5
CRAP
0.00% covered (danger)
0.00%
0 / 1
File
77.50% covered (warning)
77.50%
31 / 40
20.00% covered (danger)
20.00%
1 / 5
21.69
0.00% covered (danger)
0.00%
0 / 1
 __construct
55.56% covered (warning)
55.56%
5 / 9
0.00% covered (danger)
0.00%
0 / 1
9.16
 read
71.43% covered (warning)
71.43%
5 / 7
0.00% covered (danger)
0.00%
0 / 1
3.21
 destroy
80.00% covered (warning)
80.00%
4 / 5
0.00% covered (danger)
0.00%
0 / 1
2.03
 gc
100.00% covered (success)
100.00%
6 / 6
100.00% covered (success)
100.00%
1 / 1
3
 saveSession
84.62% covered (warning)
84.62%
11 / 13
0.00% covered (danger)
0.00%
0 / 1
4.06
1<?php
2
3/**
4 * File-based session handler
5 *
6 * PHP version 8
7 *
8 * Copyright (C) Villanova University 2010.
9 *
10 * This program is free software; you can redistribute it and/or modify
11 * it under the terms of the GNU General Public License version 2,
12 * as published by the Free Software Foundation.
13 *
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17 * GNU General Public License for more details.
18 *
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
22 *
23 * @category VuFind
24 * @package  Session_Handlers
25 * @author   Demian Katz <demian.katz@villanova.edu>
26 * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
27 * @link     https://vufind.org/wiki/development:plugins:session_handlers Wiki
28 */
29
30namespace VuFind\Session;
31
32use Laminas\Config\Config;
33
34use function function_exists;
35use function strlen;
36
37/**
38 * File-based session handler
39 *
40 * @category VuFind
41 * @package  Session_Handlers
42 * @author   Demian Katz <demian.katz@villanova.edu>
43 * @license  http://opensource.org/licenses/gpl-2.0.php GNU General Public License
44 * @link     https://vufind.org/wiki/development:plugins:session_handlers Wiki
45 */
46class File extends AbstractBase
47{
48    /**
49     * Path to session file
50     *
51     * @var string
52     */
53    protected $path;
54
55    /**
56     * Constructor
57     *
58     * @param Config $config Session configuration ([Session] section of
59     * config.ini)
60     */
61    public function __construct(Config $config = null)
62    {
63        parent::__construct($config);
64
65        // Set defaults if nothing set in config file.
66        if (isset($config->file_save_path)) {
67            $this->path = $config->file_save_path;
68        } else {
69            $tempdir = function_exists('sys_get_temp_dir')
70                ? sys_get_temp_dir() : DIRECTORY_SEPARATOR . 'tmp';
71            $this->path = $tempdir . DIRECTORY_SEPARATOR . 'vufind_sessions';
72        }
73
74        // Die if the session directory does not exist and cannot be created.
75        if (
76            (!file_exists($this->path) || !is_dir($this->path))
77            && !mkdir($this->path)
78        ) {
79            throw new \Exception('Cannot access session save path: ' . $this->path);
80        }
81    }
82
83    /**
84     * Read function must return string value always to make save handler work as
85     * expected. Return empty string if there is no data to read.
86     *
87     * @param string $sessId The session ID to read
88     *
89     * @return string
90     */
91    public function read($sessId): string
92    {
93        $sessFile = $this->path . '/sess_' . $sessId;
94        if (!file_exists($sessFile)) {
95            return '';
96        }
97
98        // enforce lifetime of this session data
99        if (filemtime($sessFile) + $this->lifetime <= time()) {
100            $this->destroy($sessId);
101            return '';
102        }
103
104        return (string)file_get_contents($sessFile);
105    }
106
107    /**
108     * The destroy handler, this is executed when a session is destroyed with
109     * session_destroy() and takes the session id as its only parameter.
110     *
111     * @param string $sessId The session ID to destroy
112     *
113     * @return bool
114     */
115    public function destroy($sessId): bool
116    {
117        // Perform standard actions required by all session methods:
118        parent::destroy($sessId);
119
120        // Perform file-specific cleanup:
121        $sessFile = $this->path . '/sess_' . $sessId;
122        if (file_exists($sessFile)) {
123            return unlink($sessFile);
124        }
125        return true;
126    }
127
128    /**
129     * The garbage collector, this is executed when the session garbage collector
130     * is executed and takes the max session lifetime as its only parameter.
131     *
132     * @param int $maxlifetime Maximum session lifetime.
133     *
134     * @return int|false
135     */
136    public function gc($maxlifetime): int|false
137    {
138        $count = 0;
139        foreach (glob($this->path . '/sess_*') as $filename) {
140            if (filemtime($filename) + $maxlifetime < time()) {
141                unlink($filename);
142                $count++;
143            }
144        }
145        return $count;
146    }
147
148    /**
149     * A function that is called internally when session data is to be saved.
150     *
151     * @param string $sessId The current session ID
152     * @param string $data   The session data to write
153     *
154     * @return bool
155     */
156    protected function saveSession($sessId, $data): bool
157    {
158        $sessFile = $this->path . '/sess_' . $sessId;
159        if ($handle = fopen($sessFile, 'w')) {
160            $return = false;
161            // Lock the file for exclusive access to avoid issues with multiple
162            // processes writing session simultaneously:
163            if (flock($handle, LOCK_EX)) {
164                $return = fwrite($handle, $data);
165                // Make sure that there's no trailing data by truncating the file to
166                // the correct length:
167                ftruncate($handle, strlen($data));
168                fflush($handle);
169                flock($handle, LOCK_UN);
170            }
171            fclose($handle);
172            if ($return !== false) {
173                return true;
174            }
175        }
176        // If we got this far, something went wrong with the file output...
177        // It is tempting to throw an exception here, but this code is called
178        // outside of the context of exception handling, so all we can do is
179        // echo a message.
180        echo 'Cannot write session to ' . $sessFile . "\n";
181        return false;
182    }
183}