Codeigniter Page cache with GET parameter - codeigniter

I am newbie to CI cache. I am facing some weird problem with codeigniter page caching. $this->output->cache(300);
I was expecting that cached version would not load if arguments in GET[] would change. But it is loading cache without considering any GET[] parameters.
I have one page where it says whether comment has been saved or not [via get parameter],
/product/product-name/?saved=true redirecting to same page where comment form is located. But it is not working. How can i invalidate old cache and create new one depending upon the get parameter? or i need to change the behavior of my comment system?
Thanks.
EDIT
Should i simply use database cache instead of Web page cache in this case?

You just have to enable the cache_query_string option in the config/config.php file.
$config['cache_query_string'] = TRUE;

Create a cache_override hook to check if there are any GET[] variables set and then skip the cache_override.
[EDIT #1]
Here is an example:
Create this file in your hooks directory:
<?php
class GetChecker {
public function checkForGet()
{
global $OUT, $CFG, $URI;
if (isset($_GET) AND ! empty($_GET))
{
return;
}
if ($OUT->_display_cache($CFG, $URI) == TRUE)
{
exit;
}
}
}
Then add this to the config/hooks.php:
$hook['cache_override'][] = array(
'class' => 'GetChecker',
'function' => 'checkForGet',
'filename' => 'GetChecker.php',
'filepath' => 'hooks'
);
I haven't tested it, it might need a little tweaking to work...

I test on CI 3+ , file system/core/Output.php 559 line, change this
if ($CI->config->item('cache_query_string') && !empty($_SERVER['QUERY_STRING']))
{
$uri .= '?'.$_SERVER['QUERY_STRING'];
}
on this
if ($CI->config->item('cache_query_string') /* && ! empty($_SERVER['QUERY_STRING']) */ && !empty($_REQUEST))
{
// $uri .= '?'.$_SERVER['QUERY_STRING'];
$uri .= '?'.http_build_query($_REQUEST);
}
And add string to your application/config/config.php
$config['cache_query_string'] = true;
it will be work with GET, POST, COOKIE ....
If need only GET, just $config['cache_query_string'] = true; - enough

I found no easier way using Hooks to prevent writing cache, as its calling _write_cache() inside the _display() method itself of CI_Output class.
For quick and easiest solution I added two conditions to display cache and write cache, if Query String parameter has variable defined( offset in my case, as I wanted for pagination)
Edit: system/core/Output.php
Add Condition to prevent writing cache, if specific GET parameter found:
function _write_cache($output)
{
if (isset($_GET['offset']) AND ! empty($_GET['offset']))
{
log_message('debug', " Don't write cache please. As as its matching condition");
return;
}
...
...
}
Add Condition to prevent displaying cache, if specific GET parameter found:
function _display_cache(&$CFG, &$URI)
{
if (isset($_GET['offset']) AND ! empty($_GET['offset']))
{
log_message('debug', " Don't display cache please. As as its matching condition");
return FALSE;
}
...
...
}

Related

TYPO3: clear cache of a page when file metadata is changed

I have a custom plugin which shows files for download based on sys_category.
When an editor changes the meta data of a file, e.g. changes the title or category, the changes are only reflected in the frontend when the complete frontend cache is cleared.
I've tried to add this to page TSconfig:
[page|uid = 0]
TCEMAIN.clearCacheCmd = 17
[global]
But this doesn't work. Any other idea how to clear the cache, when a sys_file_metadata record is changed?
Here is my solution. Thx Aristeidis for the hint.
ext_localconf.php
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['t3lib/class.t3lib_tcemain.php']['processDatamapClass']['my_extension_key'] =
\Vendor\ExtKey\Hooks\DataHandler::class;
Classes/Hooks/DataHandler.php
<?php
namespace Vendor\ExtKey\Hooks;
use TYPO3\CMS\Core\Cache\CacheManager;
use TYPO3\CMS\Core\Utility\GeneralUtility;
class DataHandler
{
public function processDatamap_afterDatabaseOperations(
$status,
$table,
$recordUid,
array $fields,
\TYPO3\CMS\Core\DataHandling\DataHandler $parentObject
) {
if ($table === 'sys_file_metadata') {
// hardcoded list of page uids to clear
$pageIdsToClear = [17];
if (!is_array($pageIdsToClear)) {
$pageIdsToClear = [(int)$pageIdsToClear];
}
$tags = array_map(function ($item) {
return 'pageId_' . $item;
}, $pageIdsToClear);
GeneralUtility::makeInstance(CacheManager::class)->flushCachesInGroupByTags('pages', $tags);
}
}
}
Of course this could be improved more:
Currently the list of page uids is hardcoded. That could be made configureable via extension manager settings.
A check could be implemented to only delete the cache if the file has a certain sys_category assigned, e.g. Downloads
But for the moment this solution is enough for my needs.

Front-End Plug-In Page

Ok, so I've looked at many posts on here about similar topics, but every single one of them is a rewrite rule for an actual php file in the url such as cart.php rather than the pretty permalink /cart/.
Basically, all I'm trying to do here is have WordPress include a template whenever a specific url is visited. In this case, example.com/cart/. Since this code is for a plug-in, it's a url that does not exist, and I cannot create a page for it. I'm trying to use a rewrite rule, along with query vars, to display the correct template when the user visits /cart/. I think I'm pretty close, but I've tried it several different ways, and I know I'm probably missing something obvious. I keep getting a 404 error, and I'm pretty sure it's my rewrite rule that is the problem. Can you guys give me a hand with this one?
Code:
/* Rewrite Rules */
add_action('init', 'llc_product_rewrite_rule');
function llc_product_rewrite_rule() {
add_rewrite_rule( 'cart/', 'index.php?llc_page=cart', 'top' );
}
/* Query Vars */
add_filter( 'query_vars', 'llc_product_register_query_var' );
function llc_product_register_query_var( $vars ) {
$vars[] = 'llc_page';
return $vars;
}
/* Parse Request */
add_action('parse_request', 'llc_cart_template');
function llc_cart_template() {
if (get_query_var('llc_page') && (get_query_var('llc_page') == "cart")) {
include plugin_dir_path(__FILE__).'inc/cart.php';
exit();
}
return;
}
Thank you for all of your well thought out responses you guys, but unfortunately I don't have all day and had to go through trial and error hell to figure this one out.
So firstly, if you want to simply have example.com/cart/ load a specific page template, when the page does not exist (for example if you need your wordpress plug-in to generate the page), this is how you'll need to set up your code. To clarify what's different from the code in my question, it's the rewrite rule, and the way I included the template. I used template_include rather than parse_request.
/* Rewrite Rules */
add_action('init', 'llc_product_rewrite_rule');
function llc_product_rewrite_rule() {
add_rewrite_rule( 'cart/?$', 'index.php?llc_page=cart', 'top' );
}
/* Query Vars */
add_filter( 'query_vars', 'llc_product_register_query_var' );
function llc_product_register_query_var( $vars ) {
$vars[] = 'llc_page';
return $vars;
}
/* Template Include */
add_filter('template_include', 'llc_cart_template_include', 1, 1);
function llc_cart_template_include($template)
{
global $wp_query;
$llc_page_value = $wp_query->query_vars['llc_page'];
if ($llc_page_value && $llc_page_value == "cart") {
return plugin_dir_path(__FILE__).'inc/cart.php';
}
return $template;
}

PHP - Global Variables

I am trying to dynamically set database connection credentials based on who logs into a web page. I'm pretty sure it's not working because of the $connectdb variable not being defined. Can someone please check out my code and try to get it working? Thanks!
<?php if ( ! defined('BASEPATH')) exit('No direct script access allowed');
$connectdb="";
class Main extends CI_Controller {
function __construct() {
parent::__construct();
echo $connectdb;
$this->load->database($connectdb);
$this->load->helper('url');
$this->load->library('grocery_CRUD');
}
public function index() {
if ($_POST["username"] == "root") {
global $connectdb="default";
}
if ($_POST["username"] == "user1") {
global $connectdb="user1";
}
if ($_POST["username"] == "user2") {
global $connectdb="user2";
}
$connect = #mysql_connect("localhost", $_POST["username"], $_POST["password"]);//won't display the warning if any.
if (!$connect) {
echo 'Server error. Please try again sometime. CON';
} else {
print("Employees");
echo "<br>";
print("Visitors");
}//Just an example to ensure that we get into the function
// LOAD LIBRARIES
}
public function employees() {
$this->grocery_crud->set_table('employees');
$output = $this->grocery_crud->render();
$this->_example_output($output);
}
public function visitors() {
$this->grocery_crud->set_table('visitors');
$output = $this->grocery_crud->render();
$this->_example_output($output);
}
function _example_output($output = null) {
$this->load->view('our_template.php',$output);
}
}
A quick read of THE MANUAL will show you it's pretty easy to have multiple database connections. You define the connection parameters in your database.php config file then call the database with the group name.
if($user == 'someguise'){
$this->load->database('guiseDB');
}
HTH
For something as important as this i would strongly suggest running the form input through CI form validation first. You really should be validating and doing things like limit the number of characters, make sure its letters only, trim whitespace and XSS cleaning - all before you do anything else. (this helps your user as well)
then to get the value from the form - do something like this
$username = $this->input->post( 'username', TRUE ) ;
and work with the one variable $username. the TRUE XSS cleans the value, and then instead of repeating
$_POST["username"] ==
over and over and over, you are just checking $username. also makes the code easier to read. if you need $username in different methods just use:
$this->username = $this->input->post( 'username', TRUE ) ;
and then $this->username will work in any method in the class.
finally consider having a table of users or config list -- and then use a different value to call your database. in other words maybe they log in with the user name: "root" but then its a different name like global $connectdb = "rootadmin"

Codeigniter - Page cache with subdomains

I am using the default Codeigniter page cache e.g.:
$this->output->cache(n);
My problem is that I am using this within two different controllers and getting a duplicate cached page i.e. the same page being returned for both. I believe this is due to using a subdomain e.g:
mobile.mysite.com => Controller 1
mysite.com => Controller 2
When I enable the cache on both, i get the same page returned.
How can I generate a different cache for each?
Regards, Ben.
By default the output cache is controller based. So as you see if a controller is named the same then it will generate or use the same cache (if you cache directory is the same in both places).
Your best workaround is using the cache driver and storing your cache manually. Here is an example of the controller code:
public function index()
{
// If we have a cache just return it and be done.
if ($mobile = $this->cache->get('page_mobile') AND $this->agent->is_mobile())
{
$this->output->set_output($mobile);
return TRUE;
}
elseif ($page = $this->cache->get('page))
{
$this->output->set_output($page);
return TRUE;
}
$vars = array();
// Save a cache and output the page.
if ($this->template->is_mobile)
{
$home = $this->load->view('page_mobile', $vars, TRUE);
$this->cache->save('controller_mobile', $home, 500);
$this->output->set_output($home);
}
else
{
$home = $this->load->view('page', $vars, TRUE);
$this->cache->save('controller', $home, 500);
$this->output->set_output($home);
}
}

Codeigniter global_xss_filtering

In my codeigniter config I have $config['global_xss_filtering'] = TRUE;. In my admin section I have a ckeditor which generates the frontend content.
Everything that is typed and placed inside the editor works fine, images are displayed nice, html is working. All except flash. Whenever I switch to html mode and paste a youtube code piece it is escaped and the code is visible on the frontpage instead of showing a youtube movie.
If I set $config['global_xss_filtering'] = FALSE; the youtube code is passed like it should. This is because 'object', 'embed' etc are flagged as "naughty" by CI and thus escaped.
How can I bypass the xss filtering for this one controller method?
Turn it off by default then enable it for places that really need it.
For example, I have it turned off for all my controllers, then enable it for comments, pages, etc.
One thing you can do is create a MY_Input (or MY_Security in CI 2) like the one in PyroCMS and override the xss_clean method with an exact copy, minus the object|embed| part of the regex.
http://github.com/pyrocms/pyrocms/blob/master/system/pyrocms/libraries/MY_Security.php
It's one hell of a long way around, but it works.
Perhaps we could create a config option could be created listing the bad elements for 2.0?
My case was that I wanted global_xss_filtering to be on by default but sometimes I needed the $_POST (pst you can do this to any global php array e.g. $_GET...) data to be raw as send from the browser, so my solution was to:
open index.php in root folder of the project
added the following line of code $unsanitized_post = $_POST; after $application_folder = 'application'; (line #92)
then whenever I needed the raw $_POST I would do the following:
global $unsanitized_post;
print_r($unsanitized_post);
In CodeIgniter 2.0 the best thing to do is to override the xss_clean on the core CI library, using MY_Security.php put this on application/core folder then using /application/config.php
$config['xss_exclude_uris'] = array('controller/method');
here's the MY_Security.php https://gist.github.com/slick2/39f54a5310e29c5a8387:
<?php
/**
* CodeIgniter version 2
* Note: Put this on your application/core folder
*/
class MY_Security extends CI_Security {
/**
* Method: __construct();
* magic
*/
function __construct()
{
parent::__construct();
}
function xss_clean($str, $is_image = FALSE)
{
$bypass = FALSE;
/**
* By pass controllers set in /application/config/config.php
* config.php
* $config['xss_exclude_uris'] = array('controller/method')
*/
$config = new CI_Config;
$uri = new CI_URI;
$uri->_fetch_uri_string();
$uri->_explode_segments();
$controllers_list = $config->item('xss_exclude_uris');
// we need controller class and method only
if (!empty($controllers_list))
{
$segments = array(0 => NULL, 1 => NULL);
$segments = $uri->segment_array();
if (!empty($segments))
{
if (!empty($segments[1]))
{
$action = $segments[0] . '/' . $segments[1];
}
else
{
$action = $segments[0];
}
if (in_array($action, $controllers_list))
{
$bypass = TRUE;
}
}
// we unset the variable
unset($config);
unset($uri);
}
if ($bypass)
{
return $str;
}
else
{
return parent::xss_clean($str, $is_image);
}
}
}
Simple do the following on the views when displaying embedded object code like from YouTube and etc:
echo str_replace(array('<', '>'), array('<', '>'), $embed_filed);
The global XSS Filtering is only escaping (or converting) certain "dangerous" html tags like <html>
Simple Workaround:
Set $config['global_xss_filtering'] = TRUE;
Run your POST data through HTMLPurifier to remove any nasty <script> tags or javascript.
HTMLPurifier Docs
HTMLPurifier Codeigniter Integration
On the page where you receive the forms POST data use html_entity_decode() to undo what XSS filtering did.
//by decoding first, we remove everything that XSS filter did
//then we encode all characters equally.
$content = html_entity_decode($this->input->post('template_content'))
Then immediately run it through htmlentities()
$content = htmlentities($content);
Store as a Blob in MySQL database
When you want to display the
information to the user for editing run html_entity_decode()
This is how I did it. If anyone knows of a major flaw in what I did, please tell me. It seems to be working fine for me. Haven't had any unexpected errors.

Resources