Skip to content

Instantly share code, notes, and snippets.

@clonemeagain
Last active May 24, 2016 01:53
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save clonemeagain/7938389 to your computer and use it in GitHub Desktop.
Save clonemeagain/7938389 to your computer and use it in GitHub Desktop.
Mod_Memcached for osTicket 1.7+ Save the below class into /includes/ Modify your main.inc.php to require_once(INCLUDE_DIR.'class.mod_memcached.php'); beneath the existing requires. View second "file" for an example uses.. the tickets.inc.php is the best.. and has the most impact on larger installs.
<?php
define ( 'MEMCACHED_LOCATION', 'localhost' ); // Change to your memcached implementation.. should be possible to access multiple memcache servers in future if necessary.
define ( 'MEMCACHED_PORT', '11211' ); // Port number. Don't forget to allow this over the network if not local.
define ( 'MEMCACHED_PREFIX', 'ost-' ); // must be unique on server This references osTickets specifically.
define ( 'MEMCACHED_DEFAULT_TIMEOUT', 3600 ); // 1 hour.
define ( 'MEMCACHED_DEBUGMODE', true ); // set to true to show cache deletions and populations in apache logs.
/**
* Abstract extendible class containing PHP Memcached functions that facilitate and ease use for applications and mini-calls.
* ;-)
*
*
* @author Grizly
*
*/
abstract class Mod_Memcached {
private $cache;
public $active;
function Mod_Memcached() {
$this->connect ();
}
public function connect() {
$active = false;
$this->cache = new Memcache ();
if ($this->cache->connect ( MEMCACHED_LOCATION, MEMCACHED_PORT ) == true) {
$this->active = true;
} else {
if (MEMCACHED_DEBUGMODE)
error_log ( "Mod_Memcached->connect(): Unable to connect to Memcached" );
}
}
/**
* Populates the Memcached memory space with a keyed value.
* If you have a long string for the key, I suggest running md5($key) instead, collisions could be costly and space is at a premium.
*
* @param string $key
* @param unknown $value
* @param bool $compressed
* optional value that will store data in zlib compressed format.
*/
function set($key, $value, $compressed = false) {
if ($compressed) {
return $this->cache->set ( MEMCACHED_PREFIX . $key, array ($key => $value,'time' => time () ), MEMCACHE_COMPRESSED, MEMCACHED_DEFAULT_TIMEOUT );
}
if (MEMCACHED_DEBUGMODE)
error_log ( "Mod_Memcached->Set(" . MEMCACHED_PREFIX . $key . ")" );
return $this->cache->set ( MEMCACHED_PREFIX . $key, array ($key => $value,'time' => time () ), false, MEMCACHED_DEFAULT_TIMEOUT );
}
/**
* Retrieve a value from the cache.
* Returns false if expired. (so don't store booleans in here.. honestly, who would do that anyway?)
*
* @param unknown $key
* Unique key for the value you want to retrieve.
* @param int $age
* in seconds to determine if cache should be invalidated by time. Default is 1-hour if left to default.
* @param bool $compressed
* option to specify that the data stored is in zlib format (corrollary to set compressed option)
* @return Ambigous <boolean, string, object, array, anything really.>
*/
function get($key, $age = MEMCACHED_DEFAULT_TIMEOUT, $compressed = false) {
if ($compressed) {
$cached = $this->cache->get ( MEMCACHED_PREFIX . $key, MEMCACHE_COMPRESSED );
} else {
$cached = $this->cache->get ( MEMCACHED_PREFIX . $key );
}
return ((time () - $cached ['time']) < $age) ? $cached [$key] : false;
}
/**
* Read: http://php.net/manual/en/memcache.flush.php
* Pretty brutal to cache..
* however, it works.
*/
function flush() {
if (MEMCACHED_DEBUGMODE)
error_log ( "Mod_Memcached-> Cache being flushed!" );
$this->cache->flush ();
}
}
/**
* Allows memcaching of "pages" as defined by the osTicket administrators.
* Saves requests to DB and rendering etc.. simply grab it from the memcache!
*
* @author Grizly
*
* You must implement the part that generates the cache and call $this->connect(); in your constructor.
*
* I've simply decided to "not publish" the pages I want my staff to be using, and have embedded them within an existing page, this way "private" information is not made
* accidentally public.
*
* IE: //inside /scp/tickets.php (after header) Check if current User is in the Sales Team:
* if($thisstaff->isTeamMember(2)){
* print new Mod_Page_Memcached(4);
* }
*
* But can be called as simply as:
* print new Mod_Page_Memcached($pageNumber);
*
*
*
* Constructor called with the number of the page-id, this means you can print the page
*/
class Mod_Page_Memcached extends Mod_Memcached {
private $pageHTML;
public function Mod_Page_Memcached($pageNumber) {
$this->connect ();
if (! $this->active) {
if (MEMCACHED_DEBUGMODE)
error_log ( "Mod_Page_Memcached::Fallback Mode!" );
$tmp = new Page ( $pageNumber );
$this->pageHTML = $this->txtToHTML ( $tmp->getBody () );
} else {
$cacheable = $this->get ( 'mod-page-' . $pageNumber );
if ($cacheable) {
$this->pageHTML = $cacheable;
} else {
if (MEMCACHED_DEBUGMODE)
error_log ( "Mod_Page_Memcached:: Generating cache for Page($pageNumber)" );
$page = new Page ( $pageNumber );
$this->pageHTML = $this->txtToHTML ( $page->getBody () );
$this->set ( 'mod-page-' . $pageNumber, $this->pageHTML );
}
}
}
// Probably shouldn't put HTML inside the class.. you should also probably use CSS classes instead of inline styles. ;-)
private function txtToHTML($text) {
return '<div class="mod-page" style="position:absolute; width:300px; height:300px; top: 10px; left: 10px; z-index: -1; ">' . $text . '</div>';
}
public function __toString() {
return $this->pageHTML;
}
}
/**
* Simple class to force-flush the cache.
*
* @author awere
*
*/
class Mod_Admin_Page_Memcached extends Mod_Memcached {
private $message;
public function Mod_Admin_Page_Memcached() {
$this->connect ();
if ($this->active) {
$this->flush ();
$this->message = 'MemCached: Cache Flushed';
} else {
$this->message = 'Mod_Admin_Page_Memcached:: Unable to connect';
}
if (MEMCACHED_DEBUGMODE)
error_log ( $this->message );
}
public function __toString() {
return $this->message;
}
}
/**
* Has the world gone mad? NO..
* only use for LONG queries which would benefit from not having to be sent to MySQL server.
* Things like the closed-tickets-queue.. which, in all honesty, who gives a crap.
*
* I wouldn't cache queries that return in a ms or less, if you are waiting on data that doesn't need to be 100% up-to-date though,
* at least TRY caching it!
*
* Uses sexified output buffering to shove literally the rest of the page into the cache.. ;-)
*
* // Adapted for The Art of Web: www.the-art-of-web.com
* // Based on PHP code by Dennis Pallett: www.phpit.net
* // Please acknowledge use of this code by including this header.
*
* Well.. not a lot actually.. but you know, the concept.
*
* To clear the cache for this, simply instatiate an Mod_Admin_Page_Memcached object..
*
*
* @author Grizly
*
*/
class Mod_RandomPage_Memcached extends Mod_Memcached {
private $key;
function Mod_RandomPage_Memcached($key) {
$this->connect ();
$this->key = 'mod-random-page-' . $key;
if (! $this->active) {
if (MEMCACHED_DEBUGMODE)
error_log ( "Mod_Query_Memcached::Fallback Mode!" );
} else {
$cacheable = $this->get ( $this->key, PHP_INT_MAX ); //change this to expire the page sooner.
if ($cacheable) {
// Cache had a copy that hasn't expired yet, shove at browser and bail.
print $cacheable;
exit ();
} else {
if (MEMCACHED_DEBUGMODE)
error_log ( "Mod_RandomPage_Memcached:: Generating cache for Page($this->key)" );
// No good copy in cache, lets start building one.
// begin caching the page, we'll shovel it back to the next user without rebuilding it.
ob_start ();
register_shutdown_function ( array ($this,'cache_page' ) );
}
}
}
/**
* Callback for <a href="http://php.net/register_shutdown_function">register_shutdown_function</a>.
*
* Simply saves the generated data into memcached.. allowing us to retrieve it damn near instantly, instead of involving loads of mysql.. ;-)
*/
function cache_page() {
$this->set ( $this->key, ob_get_contents () );
}
}
<?php
/*****************************************************************************************************************************/
Place this after the header inside /scp/tickets.php and change the values to match your requirements:
/*****************************************************************************************************************************/
$sales_team_id = 2;
$page_to_display_id = 4;
//Sales team is id #2
//Check if current User is in the Sales Team
if($thisstaff->isTeamMember($sales_team_id)){
//display cached page behind tickets for members of sales team.
//Tell specific staff with permission to modify them to add specials to the page and now all sales staff see them simultaneously.
//TODO: change style to CSS
print '<div style="position:absolute; width:300px; height:300px; top: 10px; left: 10px; z-index: -1; ">'. new Mod_Page_Memcached($page_to_display_id).'</div>';
}
/*****************************************************************************************************************************/
Place this marked line in /scp/pages.php: (allows modifications to page to invalidate the cache.)
/*****************************************************************************************************************************/
elseif($page->update($_POST, $errors)) {
$msg='Page updated successfully';
$msg .= ' ' . new Mod_Admin_Page_Memcached($_POST['id']); //MOD_MEMCACHE ADDITION <-- Add this line to clear the cached page on changes.
/*****************************************************************************************************************************/
This is a great one,
Modify include/staff/tickets.inc.php
Add the lines between the $_SESSION and the db_query
This will basically cache the entire deleted items page into memcache, and save you rendering it for each and every user.. makes a LOT OF DIFFERENCE!
Seriously, we are at over 10k tickets, and that deleted items page takes FOREVER to load..
/*****************************************************************************************************************************/
$_SESSION['search_'.$hash] = $query;
if($status == 'closed'){
//Query is quite large for these pages, they could benefit from some Caching!
//We'll save a copy per department.. that way, they don't accidentally see the wrong department or something.. ;-)
//Modified class.ticket.php::close() to invalidate this cached data.
//The $hash, ensures that all the sorting and whatnot are preserved inside the cached data..
new Mod_RandomPage_Memcached('closed-'.$thisstaff->getDeptId().'-'.$hash);
}
$res = db_query($query);
/*****************************************************************************************************************************/
Then add the following to the end of class.ticket.php's close() function, before the return.
/*****************************************************************************************************************************/
//Invalidate the page-caches, so Closed-Tickets view will be regenerated on next view.
new Mod_Admin_Page_Memcached();
/*****************************************************************************************************************************/
Wanna cache your KB's for clients?
Open /kb/index.php and add this to the top:
//MOD_Memcache
require_once('../include/class.mod_memcached.php');
new Mod_RandomPage_Memcached(__FILE__ . md5(implode(' ', $_GET)));
//ENDMOD
You can add the same to faq.php in that directory too.
To add the caching to the client index page.. which.. AFAIK doesn't actually help much, simply drop the ../ in front of the class.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment