1<?php
2/* Copyright (C) 2009-2010 Laurent Destailleur  <eldy@users.sourceforge.net>
3 * Copyright (C) 2021       Frédéric France     <frederic.france@netlogic.fr>
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 3 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program. If not, see <https://www.gnu.org/licenses/>.
17 * or see https://www.gnu.org/
18 */
19
20/**
21 *  \file		htdocs/core/lib/memory.lib.php
22 *  \brief		Set of function for memory/cache management
23 */
24
25global $shmkeys, $shmoffset;
26
27$shmkeys = array(
28	'main' => 1,
29	'admin' => 2,
30	'dict' => 3,
31	'companies' => 4,
32	'suppliers' => 5,
33	'products' => 6,
34	'commercial' => 7,
35	'compta' => 8,
36	'projects' => 9,
37	'cashdesk' => 10,
38	'agenda' => 11,
39	'bills' => 12,
40	'propal' => 13,
41	'boxes' => 14,
42	'banks' => 15,
43	'other' => 16,
44	'errors' => 17,
45	'members' => 18,
46	'ecm' => 19,
47	'orders' => 20,
48	'users' => 21,
49	'help' => 22,
50	'stocks' => 23,
51	'interventions' => 24,
52	'donations' => 25,
53	'contracts' => 26,
54);
55$shmoffset = 1000; // Max number of entries found into a language file. If too low, some entries will be overwritten.
56
57
58
59/**
60 * 	Save data into a memory area shared by all users, all sessions on server
61 *
62 *  @param	string      $memoryid		Memory id of shared area
63 * 	@param	mixed		$data			Data to save. It must not be a null value.
64 *  @param 	int			$expire			ttl in seconds, 0 never expire
65 * 	@return	int							<0 if KO, 0 if nothing is done, Nb of bytes written if OK
66 *  @see dol_getcache()
67 */
68function dol_setcache($memoryid, $data, $expire = 0)
69{
70	global $conf;
71	$result = 0;
72
73	if (strpos($memoryid, 'count_') === 0) {	// The memoryid key start with 'count_...'
74		if (empty($conf->global->MAIN_CACHE_COUNT)) {
75			return 0;
76		}
77	}
78
79	if (!empty($conf->memcached->enabled) &&  class_exists('Memcached')) {
80		// Using a memcached server
81		global $dolmemcache;
82		if (empty($dolmemcache) || !is_object($dolmemcache)) {
83			$dolmemcache = new Memcached();
84			$tmparray = explode(':', $conf->global->MEMCACHED_SERVER);
85			$result = $dolmemcache->addServer($tmparray[0], $tmparray[1] ? $tmparray[1] : 11211);
86			if (!$result) {
87				return -1;
88			}
89		}
90
91		$memoryid = session_name() . '_' . $memoryid;
92		//$dolmemcache->setOption(Memcached::OPT_COMPRESSION, false);
93		$dolmemcache->add($memoryid, $data, $expire); // This fails if key already exists
94		$rescode = $dolmemcache->getResultCode();
95		if ($rescode == 0) {
96			return is_countable($data) ? count($data) : 0;
97		} else {
98			return -$rescode;
99		}
100	} elseif (!empty($conf->memcached->enabled) && class_exists('Memcache')) {	// This is a really not reliable cache ! Use Memcached instead.
101		// Using a memcache server
102		global $dolmemcache;
103		if (empty($dolmemcache) || !is_object($dolmemcache)) {
104			$dolmemcache = new Memcache();
105			$tmparray = explode(':', $conf->global->MEMCACHED_SERVER);
106			$result = $dolmemcache->addServer($tmparray[0], $tmparray[1] ? $tmparray[1] : 11211);
107			if (!$result) {
108				return -1;
109			}
110		}
111
112		$memoryid = session_name() . '_' . $memoryid;
113		//$dolmemcache->setOption(Memcached::OPT_COMPRESSION, false);
114		$result = $dolmemcache->add($memoryid, $data, false, $expire); // This fails if key already exists
115		if ($result) {
116			return is_countable($data) ? count($data) : 0;
117		} else {
118			return -1;
119		}
120	} elseif (isset($conf->global->MAIN_OPTIMIZE_SPEED) && ($conf->global->MAIN_OPTIMIZE_SPEED & 0x02)) {	// This is a really not reliable cache ! Use Memcached instead.
121		// Using shmop
122		$result = dol_setshmop($memoryid, $data, $expire);
123	}
124
125	return $result;
126}
127
128/**
129 * 	Read a memory area shared by all users, all sessions on server
130 *
131 *  @param	string	$memoryid		Memory id of shared area
132 * 	@return	int|mixed				<0 if KO, data if OK, null if not found into cache or no caching feature enabled
133 *  @see dol_setcache()
134 */
135function dol_getcache($memoryid)
136{
137	global $conf;
138
139	if (strpos($memoryid, 'count_') === 0) {	// The memoryid key start with 'count_...'
140		if (empty($conf->global->MAIN_CACHE_COUNT)) {
141			return null;
142		}
143	}
144
145	// Using a memcached server
146	if (!empty($conf->memcached->enabled) && class_exists('Memcached')) {
147		global $m;
148		if (empty($m) || !is_object($m)) {
149			$m = new Memcached();
150			$tmparray = explode(':', $conf->global->MEMCACHED_SERVER);
151			$result = $m->addServer($tmparray[0], $tmparray[1] ? $tmparray[1] : 11211);
152			if (!$result) {
153				return -1;
154			}
155		}
156
157		$memoryid = session_name() . '_' . $memoryid;
158		//$m->setOption(Memcached::OPT_COMPRESSION, false);
159		//print "Get memoryid=".$memoryid;
160		$data = $m->get($memoryid);
161		$rescode = $m->getResultCode();
162		//print "memoryid=".$memoryid." - rescode=".$rescode." - count(response)=".count($data)."\n<br>";
163		//var_dump($data);
164		if ($rescode == 0) {
165			return $data;
166		} elseif ($rescode == 16) {		// = Memcached::MEMCACHED_NOTFOUND but this constant doe snot exists.
167			return null;
168		} else {
169			return -$rescode;
170		}
171	} elseif (!empty($conf->memcached->enabled) && class_exists('Memcache')) {	// This is a really not reliable cache ! Use Memcached instead.
172		global $m;
173		if (empty($m) || !is_object($m)) {
174			$m = new Memcache();
175			$tmparray = explode(':', $conf->global->MEMCACHED_SERVER);
176			$result = $m->addServer($tmparray[0], $tmparray[1] ? $tmparray[1] : 11211);
177			if (!$result) {
178				return -1;
179			}
180		}
181
182		$memoryid = session_name() . '_' . $memoryid;
183		//$m->setOption(Memcached::OPT_COMPRESSION, false);
184		$data = $m->get($memoryid);
185		//print "memoryid=".$memoryid." - rescode=".$rescode." - data=".count($data)."\n<br>";
186		//var_dump($data);
187		if ($data) {
188			return $data;
189		} else {
190			return null;		// There is no way to make a difference between NOTFOUND and error when using Memcache. So do not use it, use Memcached instead.
191		}
192	} elseif (isset($conf->global->MAIN_OPTIMIZE_SPEED) && ($conf->global->MAIN_OPTIMIZE_SPEED & 0x02)) {	// This is a really not reliable cache ! Use Memcached instead.
193		// Using shmop
194		$data = dol_getshmop($memoryid);
195		return $data;
196	}
197
198	return null;
199}
200
201
202
203/**
204 * 	Return shared memory address used to store dataset with key memoryid
205 *
206 *  @param	string	$memoryid		Memory id of shared area ('main', 'agenda', ...)
207 * 	@return	int						<0 if KO, Memoy address of shared memory for key
208 */
209function dol_getshmopaddress($memoryid)
210{
211	global $shmkeys, $shmoffset;
212	if (empty($shmkeys[$memoryid])) {	// No room reserved for thid memoryid, no way to use cache
213		return 0;
214	}
215	return $shmkeys[$memoryid] + $shmoffset;
216}
217
218/**
219 * 	Return list of contents of all memory area shared
220 *
221 * 	@return	array
222 */
223function dol_listshmop()
224{
225	global $shmkeys, $shmoffset;
226
227	$resarray = array();
228	foreach ($shmkeys as $key => $val) {
229		$result = dol_getshmop($key);
230		if (!is_numeric($result) || $result > 0) {
231			$resarray[$key] = $result;
232		}
233	}
234	return $resarray;
235}
236
237/**
238 * 	Save data into a memory area shared by all users, all sessions on server
239 *
240 *  @param	int		$memoryid		Memory id of shared area ('main', 'agenda', ...)
241 * 	@param	string	$data			Data to save. Must be a not null value.
242 *  @param 	int		$expire			ttl in seconds, 0 never expire
243 * 	@return	int						<0 if KO, 0=Caching not available, Nb of bytes written if OK
244 */
245function dol_setshmop($memoryid, $data, $expire)
246{
247	global $shmkeys, $shmoffset;
248
249	//print 'dol_setshmop memoryid='.$memoryid."<br>\n";
250	if (empty($shmkeys[$memoryid]) || !function_exists("shmop_write")) {
251		return 0;
252	}
253	$shmkey = dol_getshmopaddress($memoryid);
254	if (empty($shmkey)) {
255		return 0;	// No key reserved for this memoryid, we can't cache this memoryid
256	}
257
258	$newdata = serialize($data);
259	$size = strlen($newdata);
260	//print 'dol_setshmop memoryid='.$memoryid." shmkey=".$shmkey." newdata=".$size."bytes<br>\n";
261	$handle = shmop_open($shmkey, 'c', 0644, 6 + $size);
262	if ($handle) {
263		$shm_bytes_written1 = shmop_write($handle, str_pad($size, 6), 0);
264		$shm_bytes_written2 = shmop_write($handle, $newdata, 6);
265		if (($shm_bytes_written1 + $shm_bytes_written2) != (6 + dol_strlen($newdata))) {
266			print "Couldn't write the entire length of data\n";
267		}
268		shmop_close($handle);
269		return ($shm_bytes_written1 + $shm_bytes_written2);
270	} else {
271		print 'Error in shmop_open for memoryid=' . $memoryid . ' shmkey=' . $shmkey . ' 6+size=6+' . $size;
272		return -1;
273	}
274}
275
276/**
277 * 	Read a memory area shared by all users, all sessions on server
278 *
279 *  @param	string	$memoryid		Memory id of shared area ('main', 'agenda', ...)
280 * 	@return	int						<0 if KO, data if OK, Null if no cache enabled or not found
281 */
282function dol_getshmop($memoryid)
283{
284	global $shmkeys, $shmoffset;
285
286	$data = null;
287
288	if (empty($shmkeys[$memoryid]) || !function_exists("shmop_open")) {
289		return null;
290	}
291	$shmkey = dol_getshmopaddress($memoryid);
292	if (empty($shmkey)) {
293		return null;		// No key reserved for this memoryid, we can't cache this memoryid
294	}
295
296	//print 'dol_getshmop memoryid='.$memoryid." shmkey=".$shmkey."<br>\n";
297	$handle = @shmop_open($shmkey, 'a', 0, 0);
298	if ($handle) {
299		$size = trim(shmop_read($handle, 0, 6));
300		if ($size) {
301			$data = unserialize(shmop_read($handle, 6, $size));
302		} else {
303			return -1;
304		}
305		shmop_close($handle);
306	} else {
307		return null;	// Can't open existing block, so we suppose it was not created, so nothing were cached yet for the memoryid
308	}
309	return $data;
310}
311