1 <?php
  2 /**
  3  * @package     Joomla.Platform
  4  * @subpackage  Cache
  5  *
  6  * @copyright   Copyright (C) 2005 - 2017 Open Source Matters, Inc. All rights reserved.
  7  * @license     GNU General Public License version 2 or later; see LICENSE
  8  */
  9 
 10 defined('JPATH_PLATFORM') or die;
 11 
 12 /**
 13  * APC cache storage handler
 14  *
 15  * @link   https://secure.php.net/manual/en/book.apc.php
 16  * @since  11.1
 17  */
 18 class JCacheStorageApc extends JCacheStorage
 19 {
 20     /**
 21      * Check if the cache contains data stored by ID and group
 22      *
 23      * @param   string  $id     The cache data ID
 24      * @param   string  $group  The cache data group
 25      *
 26      * @return  boolean
 27      *
 28      * @since   3.7.0
 29      */
 30     public function contains($id, $group)
 31     {
 32         return apc_exists($this->_getCacheId($id, $group));
 33     }
 34 
 35     /**
 36      * Get cached data by ID and group
 37      *
 38      * @param   string   $id         The cache data ID
 39      * @param   string   $group      The cache data group
 40      * @param   boolean  $checkTime  True to verify cache time expiration threshold
 41      *
 42      * @return  mixed  Boolean false on failure or a cached data object
 43      *
 44      * @since   11.1
 45      */
 46     public function get($id, $group, $checkTime = true)
 47     {
 48         return apc_fetch($this->_getCacheId($id, $group));
 49     }
 50 
 51     /**
 52      * Get all cached data
 53      *
 54      * @return  mixed  Boolean false on failure or a cached data object
 55      *
 56      * @since   11.1
 57      */
 58     public function getAll()
 59     {
 60         $allinfo = apc_cache_info('user');
 61         $keys    = $allinfo['cache_list'];
 62         $secret  = $this->_hash;
 63 
 64         $data = array();
 65 
 66         foreach ($keys as $key)
 67         {
 68             if (isset($key['info']))
 69             {
 70                 // If APCu is being used for this adapter, the internal key name changed with APCu 4.0.7 from key to info
 71                 $name = $key['info'];
 72             }
 73             elseif (isset($key['entry_name']))
 74             {
 75                 // Some APC modules changed the internal key name from key to entry_name, HHVM is one such case
 76                 $name = $key['entry_name'];
 77             }
 78             else
 79             {
 80                 // A fall back for the old internal key name
 81                 $name = $key['key'];
 82             }
 83 
 84             $namearr = explode('-', $name);
 85 
 86             if ($namearr !== false && $namearr[0] == $secret && $namearr[1] == 'cache')
 87             {
 88                 $group = $namearr[2];
 89 
 90                 if (!isset($data[$group]))
 91                 {
 92                     $item = new JCacheStorageHelper($group);
 93                 }
 94                 else
 95                 {
 96                     $item = $data[$group];
 97                 }
 98 
 99                 $item->updateSize($key['mem_size']);
100 
101                 $data[$group] = $item;
102             }
103         }
104 
105         return $data;
106     }
107 
108     /**
109      * Store the data to cache by ID and group
110      *
111      * @param   string  $id     The cache data ID
112      * @param   string  $group  The cache data group
113      * @param   string  $data   The data to store in cache
114      *
115      * @return  boolean
116      *
117      * @since   11.1
118      */
119     public function store($id, $group, $data)
120     {
121         return apc_store($this->_getCacheId($id, $group), $data, $this->_lifetime);
122     }
123 
124     /**
125      * Remove a cached data entry by ID and group
126      *
127      * @param   string  $id     The cache data ID
128      * @param   string  $group  The cache data group
129      *
130      * @return  boolean
131      *
132      * @since   11.1
133      */
134     public function remove($id, $group)
135     {
136         return apc_delete($this->_getCacheId($id, $group));
137     }
138 
139     /**
140      * Clean cache for a group given a mode.
141      *
142      * group mode    : cleans all cache in the group
143      * notgroup mode : cleans all cache not in the group
144      *
145      * @param   string  $group  The cache data group
146      * @param   string  $mode   The mode for cleaning cache [group|notgroup]
147      *
148      * @return  boolean
149      *
150      * @since   11.1
151      */
152     public function clean($group, $mode = null)
153     {
154         $allinfo = apc_cache_info('user');
155         $keys    = $allinfo['cache_list'];
156         $secret  = $this->_hash;
157 
158         foreach ($keys as $key)
159         {
160             if (isset($key['info']))
161             {
162                 // If APCu is being used for this adapter, the internal key name changed with APCu 4.0.7 from key to info
163                 $internalKey = $key['info'];
164             }
165             elseif (isset($key['entry_name']))
166             {
167                 // Some APC modules changed the internal key name from key to entry_name, HHVM is one such case
168                 $internalKey = $key['entry_name'];
169             }
170             else
171             {
172                 // A fall back for the old internal key name
173                 $internalKey = $key['key'];
174             }
175 
176             if (strpos($internalKey, $secret . '-cache-' . $group . '-') === 0 xor $mode != 'group')
177             {
178                 apc_delete($internalKey);
179             }
180         }
181 
182         return true;
183     }
184 
185     /**
186      * Garbage collect expired cache data
187      *
188      * @return  boolean
189      *
190      * @since   11.1
191      */
192     public function gc()
193     {
194         $allinfo = apc_cache_info('user');
195         $keys    = $allinfo['cache_list'];
196         $secret  = $this->_hash;
197 
198         foreach ($keys as $key)
199         {
200             if (isset($key['info']))
201             {
202                 // If APCu is being used for this adapter, the internal key name changed with APCu 4.0.7 from key to info
203                 $internalKey = $key['info'];
204             }
205             elseif (isset($key['entry_name']))
206             {
207                 // Some APC modules changed the internal key name from key to entry_name, HHVM is one such case
208                 $internalKey = $key['entry_name'];
209             }
210             else
211             {
212                 // A fall back for the old internal key name
213                 $internalKey = $key['key'];
214             }
215 
216             if (strpos($internalKey, $secret . '-cache-'))
217             {
218                 apc_fetch($internalKey);
219             }
220         }
221     }
222 
223     /**
224      * Test to see if the storage handler is available.
225      *
226      * @return  boolean
227      *
228      * @since   12.1
229      */
230     public static function isSupported()
231     {
232         $supported = extension_loaded('apc') && ini_get('apc.enabled');
233 
234         // If on the CLI interface, the `apc.enable_cli` option must also be enabled
235         if ($supported && php_sapi_name() === 'cli')
236         {
237             $supported = ini_get('apc.enable_cli');
238         }
239 
240         return (bool) $supported;
241     }
242 
243     /**
244      * Lock cached item
245      *
246      * @param   string   $id        The cache data ID
247      * @param   string   $group     The cache data group
248      * @param   integer  $locktime  Cached item max lock time
249      *
250      * @return  mixed  Boolean false if locking failed or an object containing properties lock and locklooped
251      *
252      * @since   11.1
253      */
254     public function lock($id, $group, $locktime)
255     {
256         $returning             = new stdClass;
257         $returning->locklooped = false;
258 
259         $looptime = $locktime * 10;
260 
261         $cache_id = $this->_getCacheId($id, $group) . '_lock';
262 
263         $data_lock = apc_add($cache_id, 1, $locktime);
264 
265         if ($data_lock === false)
266         {
267             $lock_counter = 0;
268 
269             // Loop until you find that the lock has been released. That implies that data get from other thread has finished
270             while ($data_lock === false)
271             {
272                 if ($lock_counter > $looptime)
273                 {
274                     $returning->locked     = false;
275                     $returning->locklooped = true;
276                     break;
277                 }
278 
279                 usleep(100);
280                 $data_lock = apc_add($cache_id, 1, $locktime);
281                 $lock_counter++;
282             }
283         }
284 
285         $returning->locked = $data_lock;
286 
287         return $returning;
288     }
289 
290     /**
291      * Unlock cached item
292      *
293      * @param   string  $id     The cache data ID
294      * @param   string  $group  The cache data group
295      *
296      * @return  boolean
297      *
298      * @since   11.1
299      */
300     public function unlock($id, $group = null)
301     {
302         return apc_delete($this->_getCacheId($id, $group) . '_lock');
303     }
304 }
305