Zend Framework Paginator cache doesn't work (no hit) - php

I use a file based cache to cache a paginator:
$table = $this->getDbTable();
$ret = $table ->select()
->from($table,array('id',
'UNIX_TIMESTAMP(`date`) as date',
'categoryId',
'title',
'teaser'))
->where('`categoryId`=?',$cat)
->order('date desc');
$adapter = new Zend_Paginator_Adapter_DbTableSelect($ret);
$paginator = new Zend_Paginator($adapter);
$fO = array('lifetime' => 3600, 'automatic_serialization' => true);
$bO = array('cache_dir'=>APPLICATION_PATH . '/cache');
$cache = Zend_cache::factory('Core', 'File', $fO, $bO);
Zend_Paginator::setCache($cache);
$paginator->setItemCountPerPage(5);
$paginator->setCurrentPageNumber($page);
$this->view->paginator = $paginator;
Now every request, the paginator creates a neew cache entry and ignors the old one created? Any ideas?

I had some similar problems with Zend_Paginator. These problems only come with Zend_Paginator_Adapter_DbTableSelect (I had no problems with Zend_Paginator_Adapter_Array).
The bolded part of the cache filename zend_cache---Zend_Paginator_3_2b49905a9282f742e1cefafc53892794 is made by _getCacheId function (check Zend_Paginator) based on the adapter passed to the constructor. On each request, the paginator creates a new code because the adapter is never the same at the $_cache->load moment and $_cache->save moment.
The md5 serialized value of the adapter used as filename when data is saved into cache is different from the one used when data is read from cache if you have DB profiler enabled. You must not use DB profiler in order for the Zend_Paginator cache to work (use it only in development stage).
Another failure reason that I have found is determined by these two lines of code:
$offset = ($pageNumber - 1) * $this->getItemCountPerPage();
$items = $this->_adapter->getItems($offset, $this->getItemCountPerPage());
They are called between $_cache->load and $_cache->save and they add a limitcount value and a limitoffset value to adapter. This values aren't set when $_cache->load is called so the filename based on the md5 serialized value of the adapter will be different in this case too.
They must be placed before $_cache->load. You can make a paginator class that extends Zend_Paginator and modify getItemsByPage function. Add the two lines at the beggining, after $pageNumber = $this->normalizePageNumber($pageNumber).
This worked for me. I hope it will help others too.

Related

Limit the doctrine pagination total records in Zend Framework

I have a table with hundreds of thousand registers. I´m doing a pagination page with search capabilities using Zend Framework 3 and Doctrine 2. My problem is that I would like to limit the total number of registers to 500 independent of the search parameters and the pagination.
On the Controller:
// This will return the result of DQL
$registros = $this->regService->getRegisters($searchParameters);
$adapter = new DoctrineAdapter(new ORMPaginator($registros, false));
$paginator = new Paginator($adapter);
$paginator->setDefaultItemCountPerPage(20);
$paginator->setCurrentPageNumber($page);
return new ViewModel([
'registros' => $paginator
]);
So, the problem is that if I do a search with parameters thar get 2 thousand registers, the pagination will return all these registers and what I want to do is limit in 500 register
Thanks and best regards
From the code I'm not quite sure where your problem is; the implementation of the getRegisters($param) is missing.
However, to implement pagination you're on the right path.
Try the following:
$page = $this->params()->fromQuery('page', 1);
$itemsPerPage = $this->params()->fromQuery('itemsPerPage', null);
// I'm assuming your `getRegisters($param)` function returns QueryBuilder object
/** #var QueryBuilder $qb */
$qb = $this->getEntityManager()->createQueryBuilder();
// This query gets everything from Entity "Entity"
$qb->select('shortEntityName')
->from(\Namespace\To\Entity::class, 'shortEntityName');
// Create a Paginator, pass the QueryBuilder as parameter
$paginator = new Paginator(new OrmAdapter(new OrmPaginator($qb)));
// Set the current page (should be received from URL GET request)
$paginator->setCurrentPageNumber($page);
// Set the max items per page, defaulting to max of 500
$paginator->setItemCountPerPage($itemsPerPage ?: 500);
return [
'paginator' => $paginator,
];
Next to this you should also have a 'pagination' partial template somewhere you use for the pagination navigation, to use in the view. This template should accommodate the way you add the different GET parameters, e.g. &page=2 for the second page or &itemsPerPage=100 to show 100 items on a page instead of 500.
Based on comment, slightly revised last functions on the $paginator.
// If using code above, you can discard getting "itemsPerPage" from URL, no longer required
// Set the current page (should be received from URL GET request)
$paginator->setCurrentPageNumber(($page <= 10) ? $page : 10); // max allowed = 10
// Set the max items per page, defaulting to max of 500
$paginator->setItemCountPerPage(50); // always 50 per page

Activate a Topic for a Page programmaticaly concrete5 5.7

The goal is to activate an existent topic in a Blog Entry page. Normally a user does this in the Pages Attributes section like so:
Now my goal is to do this programmaticaly. I won't post all my trials (since 2 days) here because it's just crap, but here's what I've done so far.
First I add a Blog Page to a chosen parent Page (ID 157):
use Concrete\Core\Page;
$parentPage = Page\Page::getByID(157);
$template = \PageTemplate::getByHandle('blog_entry');
$entry = $parentPage->add($type, array(
'cName' => 'My title',
'cDescription' => 'description',
'cHandle' => 'my_title',
'cvIsApproved' => true,
'cDatePublic' => $publishDate->format('Y-m-d H:i:s')
), $template);
As the newly created page is a blog_entry template the Blog Entry Topics is already assigned.
Then I create a Topic and add it to its Topic Tree (Blog Entry Topics) like so:
use \Concrete\Core\Tree\Type\Topic as TopicTree;
use \Concrete\Core\Tree\Node\Type\Topic as TopicTreeNode;
use \Concrete\Core\Tree\Node\Node as TreeNode;
$topicTree = TopicTree::getByName('Blog Entries');
$parentTopic = TreeNode::getByID($topicTree->getRootTreeNodeObject()->treeNodeID);
$item0 = TopicTreeNode::add('udland', $parentTopic);
How to activate/assign this Topic(Udland) to my page ($entry)? (As shown in the image)
I know it must be related to the DB-tables CollectionAttributeValues and atSelectedTopics. Also the Classes CollectionValue and CollectionKey must be involved.
I could add those entries manually in the DB but this isn't a good idea because I don't know what data is necessary to make this work correctly. The topics are used to filter Blog entries so I'm quite sure that there are other tables involved and as a Core developer said: "These are fragile little things" ;-).
As this version of concrete5 is a complete new launch, the developer docs aren't complete and after 2 days of digging inside the core code I'm just desperate.
Update (after a week of digging...)
I managed to do a hack taken out of a Controller method: (/concrete/controllers/panel/page/attributes.php -> submit()).
I know this isn't the way to go at all but it's my best trial so far:
(I just include the NameSpaces here to make clear what Classes I'm calling)
use Concrete\Core\Page;
use Concrete\Core\Page\Collection\Version\Version;
use Concrete\Core\Workflow\Request\ApprovePageRequest;
use CollectionAttributeKey;
use \Concrete\Core\Tree\Node\Type\Topic as TopicTreeNode;
Get the Attributes ID by handle:
$ak = CollectionAttributeKey::getByHandle('blog_entry_topics');
$attributekID = $ak->getAttributeKeyID();
get the topic
$item_one = TopicTreeNode::getNodeByName('Udland');
then simulate a posted form by:
$_POST = array(
'topics_' . $attributekID => array($item_one->treeNodeID)
);
I know this is so ugly and a big hack & not reliable at all but as said it's taken out of a Controller...
Then I do a slimmed version of the submit() method:
$c = Page\Page::getByID(157);
$published = new \DateTime();
$nvc = $c->getVersionToModify();
$nvcObj = $nvc->getVersionObject();
$data = array();
$data['cName'] = $nvcObj->cvName;
$data['cDescription'] = $nvcObj->cvDescription;
$data['cDatePublic'] = $published->format('Y-m-d H:i:s');
$data['uID'] = '1';
$nvc->update($data);
$setAttribs = $nvc->getSetCollectionAttributes();
$processedAttributes = array();
$selectedAKIDs = $attributekID;
if (!is_array($selectedAKIDs)) {
$selectedAKIDs = array();
}
$selected = is_array(array($attributekID)) ? array($attributekID) : array();
foreach ($setAttribs as $ak) {
if (in_array($ak->getAttributeKeyID(), $selected)) {
$ak->saveAttributeForm($nvc);
} else {
$nvc->clearAttribute($ak);
}
$processedAttributes[] = $ak->getAttributeKeyID();
}
$newAttributes = array_diff($selectedAKIDs, $processedAttributes);
foreach ($newAttributes as $akID) {
$ak = CollectionAttributeKey::getByID($akID);
$ak->saveAttributeForm($nvc);
}
So as said before this is really ugly but it's the best trial so far and somehow it works.
Then approve the Request by doing:
$pkr = new ApprovePageRequest();
$u = new User();
$pkr->setRequestedPage($c);
$v = Version::get($c, "RECENT");
$pkr->setRequestedVersionID($v->getVersionID());
$pkr->setRequesterUserID($u->getUserID());
$pkr->trigger();
$u->unloadCollectionEdit();
But what really makes me wonder is that method inside of /concrete/src/Attribute/Key/Key.php where finally the thing should happen (in my humble opinion):
/**
* Calls the functions necessary to save this attribute to the database. If no passed value is passed, then we save it via the stock form.
* NOTE: this code is screwy because all code ever written that EXTENDS this code creates an attribute value object and passes it in, like
* this code implies. But if you call this code directly it passes the object that you're messing with (Page, User, etc...) in as the $attributeValue
* object, which is obviously not right. So we're going to do a little procedural if/then checks in this to ensure we're passing the right
* stuff
*
* #param CollectionValue|mixed $mixed
* #param mixed $passedValue
*/
protected function saveAttribute($mixed, $passedValue = false)
{
/** #var \Concrete\Core\Attribute\Type $at */
$at = $this->getAttributeType();
$at->getController()->setAttributeKey($this);
if ($mixed instanceof AttributeValue) {
$attributeValue = $mixed;
} else {
// $mixed is ACTUALLY the object that we're setting the attribute against
//todo: figure out what $nvc should really be since it doesn't exist in this scope
$attributeValue = $nvc->getAttributeValueObject($mixed, true);
}
$at->getController()->setAttributeValue($attributeValue);
if ($passedValue) {
$at->getController()->saveValue($passedValue);
} else {
$at->getController()->saveForm($at->getController()->post());
}
$at->__destruct();
unset($at);
}
So I'm really curios to see what the reliable and system-suitable way is to resolve this.
Here's what I came up with that does work. You were pretty close.
use \Concrete\Core\Tree\Type\Topic as TopicTree;
use \Concrete\Core\Tree\Node\Type\Topic as TopicTreeNode;
use \Concrete\Core\Tree\Node\Node as TreeNode;
$parentPage = \Page::getbyPath('/blog');
$template = \PageTemplate::getByHandle('blog_entry');
$entry = $parentPage->add($type, array(
'cName' => 'ooops',
'cDescription' => 'hmmmm',
'cHandle' => 'yay',
'cvIsApproved' => true,
'cDatePublic' => '2015-12-21 00:00:00'
), $template);
$item0 = TopicTreeNode::getNodeByName('udland');
if (!$item0) {
$topicTree = TopicTree::getByName('Blog Entries');
$parentTopic = TreeNode::getByID($topicTree->getRootTreeNodeObject()->treeNodeID);
$item0 = TopicTreeNode::add('udland', $parentTopic);
}
$entry->setAttribute('blog_entry_topics', array($item0->getTreeNodeDisplayPath()));
It looks like the attribute takes in an array of node display paths and that is how it sets the selection. Additionally, you have to use the \Page alias, and not the fully qualified namespace as you were doing, otherwise you get an error about it being unable to clear the cache.

Azure PHP SDK: get the asset by asset name

Is it possible to get the asset details using asset name with Azure PHP sdk. I can get all the asset list, but it's loading first 1000 assets only.
getAssetList();
I can get single asset details using asset id.
getAsset($asset);
But in my case, I don't have asset id with me. I just have asset name alone. Now how do I get the asset details using this?
EDIT:
I got some help from Azure support saying that, we can use $skip parameter for pagination. I got code snippet in c#
for (int i = 0; i < _context.Assets.Count(); i += 1000 )
{
var assets = _context.Assets.Skip(i);
foreach (IAsset objIAsset in assets)
{
Console.WriteLine(objIAsset.Name.ToString());
}
}
How can I use this param in PHP SDK.
It seem that Azure SDK for PHP don't support skip method. However, I used the fiddler to monitor C# skip method and got the URL like this:
https://***-hs.cloudapp.net/api/Assets()?$skip=1000
So I think we can bulid up the request path like above in our PHP project and we can modify the getAssetList method in "MediaServicesRestProxy" file.
I add a function named "getAssetListBySkip($number)" into "MediaServicesRestProxy" class, the code like this:
/**
* Get asset list using skip number
*
* */
public function getAssetListBySkip($number)
{
$propertyList = $this->_getEntityList("Assets()?".'$skip='.$number);
$result = array();
foreach ($propertyList as $properties) {
$result[] = Asset::createFromOptions($properties);
}
return $result;
}
We can call this method like this:
$mediaServiceProxy = ServicesBuilder::getInstance()->createMediaServicesService(
new MediaServicesSettings("**","**/**="));
$result=$mediaServiceProxy->getAssetListBySkip(1000);
Azure Media services supports filtering by name. You can construct web request to be like
/api/assets()?$filter=Name%20eq%20'Your Name'&$top=1
You can also filter by other properties
Have you tried REST API that are used when creating, processing, managing, and delivering Assets. https://msdn.microsoft.com/en-us/library/azure/hh974277.aspx#list_an_asset but I do think we can list asset via a name directly since id is an unique indentifier of asset entity. PHP Azure SDK leverages assetId to get an Asset as well:
public function getAsset($asset)
{
$assetId = Utilities::getEntityId(
$asset,
'WindowsAzure\MediaServices\Models\Asset'
);
return Asset::createFromOptions($this->_getEntity("Assets('{$assetId}')"));
}
But in my case, I don't have asset id with me. I just have asset name
alone. Now how do I get the asset details using this?
Here are some test function code snippets for your reference:
public function testListAllAssets(){
// Setup
$asset1 = new Asset(Asset::OPTIONS_NONE);
$asset1->setName(TestResources::MEDIA_SERVICES_ASSET_NAME . $this->createSuffix());
$asset2 = new Asset(Asset::OPTIONS_NONE);
$asset2->setName(TestResources::MEDIA_SERVICES_ASSET_NAME . $this->createSuffix());
// Test
$asset1 = $this->createAsset($asset1);
$asset2 = $this->createAsset($asset2);
$result = $this->restProxy->getAssetList();
// Assert
$this->assertCount(2, $result);
$names = array(
$result[0]->getName(),
$result[1]->getName()
);
$id = array(
$result[0]->getId(),
$result[1]->getId()
);
$this->assertContains($asset1->getName(), $names);
$this->assertContains($asset2->getName(), $names);
$this->assertContains($asset1->getId(), $id);
$this->assertContains($asset2->getId(), $id);
}
public function testGetAnAssetReference(){
// Setup
$assetName = TestResources::MEDIA_SERVICES_ASSET_NAME . $this->createSuffix();
$asset = new Asset(Asset::OPTIONS_NONE);
$asset->setName($assetName);
$asset = $this->createAsset($asset);
// Test
$result = $this->restProxy->getAsset($asset);
// Assert
$this->assertEquals($asset->getId(), $result->getId());
$this->assertEquals($asset->getName(), $result->getName());
}
From: https://github.com/Azure/azure-sdk-for-php/blob/master/tests/functional/WindowsAzure/MediaServices/MediaServicesFunctionalTest.php
According to my testing, it seems that we can’t use Asset’s name to get the information of asset in Media Service.
$mediaServiceProxy = ServicesBuilder::getInstance()->createMediaServicesService(
new MediaServicesSettings("**","******"));
$asset = new Asset(Asset::OPTIONS_NONE);
$asset->setName('For-Test-wmv-Source');
//$asset don't have the value of id,
// unless execute ‘createAsset($asset)’, "$asset1" will be set the ID
$asset1 =$mediaServiceProxy->createAsset($asset);
$result2=$mediaServiceProxy->getAsset($asset1);
PHP SDK support the method named “getAsset($asset)”. Actually, the method get the Asset information by Asset id, just like the Aka's reference code.And Azure REST API don’t support the method queried by Asset’s name.
Please refer to the official document.
Alternative approach is that you can store your assets information (such as Id,URl,name and ect.) into Azure table storage when you upload them into media service. If you want to use it, you can fetch and filter the data of Asset’s name you wants from table storage.

setting persistent plugin parameters in Joomla 3

I'm developing a Joomla 3.x plugin, and want to be able to change the plugin parameter set in the plugin's manifest file programmatically. I believe I need to use a JRegistry object, but I'm not sure about the syntax.
Here's the issue:
// token A is set in plugin params as defined in plugin's XML manifest
var_dump($this->params->get('token')); // prints token "A" as expected
// do some stuff to get a fresh access token, called token "B"
$tokenB = $function_to_get_fresh_token();
// set the new token
if ($tokenB) $this->params->set('token', $tokenB);
var_dump($this->params->get('apptoken')); // prints token "B" as expected
the problem is that on subsequent page reloads, the token reverts to tokenA rather than what I assumed would be the stored value of tokenB.
How do I store the tokenB value in the plugin's parameters in the database?
This is a working example of how to change plugin params from within the plugin (J! 3.4):
// Load plugin called 'plugin_name'
$table = new JTableExtension(JFactory::getDbo());
$table->load(array('element' => 'plugin_name'));
// Params can be changed like this
$this->params->set('new_param', 'new value'); // if you are doing change from a plugin
$table->set('params', $this->params->toString());
// Save the change
$table->store();
Note: If new params are added by plugin dynamically and the plugin is saved afterwards, these new params gets deleted. So one way to deal with it is to add those params as hidden fields to plugin's config XML.
This is just an outline, but something along these lines
$extensionTable = new JtableExtension();
$pluginId = $extensionTable->find('element', 'my_plugin');
$pluginRow = $extensionTable->load($pluginId);
// Do the jregistry work that is needed
// do some stuff to get a fresh access token, called token "B"
$tokenB = $function_to_get_fresh_token();
// set the new token
if ($tokenB) $this->params->set('token', $tokenB);
// more stuff
$extensionTable->save($pluginRow);
I spent a lot of time googling and reading and found no real answer to this. Oddly enough this doesn't seem to have been provided for in Joomla. So here's what I ended up doing:
1) build a function to get your plugin ID, since it will change from one installation to another
private function getPlgId(){
// stupid hack since there doesn't seem to be another way to get plugin id
$db = JFactory::getDBO();
$sql = 'SELECT `extension_id` FROM `#__extensions` WHERE `element` = "my_plugin" AND `folder` = "my_plugin_folder"'; // check the #__extensions table if you don't know your element / folder
$db->setQuery($sql);
if( !($plg = $db->loadObject()) ){
return false;
} else {
return (int) $plg->extension_id;
}
}
2) use the plugin id to load the table object:
$extension = new JTableExtension($db);
$ext_id = $this->getPlgId();
// get the existing extension data
$extension->load($ext_id);
3) when you're ready to store the value, add it to the params, then store it:
$this->params->set('myvalue', $newvalue);
$extension->bind( array('params' => $this->params->toString()) );
// check and store
if (!$extension->check()) {
$this->setError($extension->getError());
return false;
}
if (!$extension->store()) {
$this->setError($extension->getError());
return false;
}
If anyone knows a better way to do this please let me know!

CodeIgniter shared data between calls to load->view

Consider a view called render_thing, which I load from a controller like so:
$html = $this->load->view(
'render_thing',
array(
'someParam' => $globalParam
'permissionMode' => 'guest'
),
true
);
log($html);
Later on in that same controller, I load the view again, except I don't override the optional permissionMode parameter. I'm assume that in the view code, $permissionMode would be unset.
$moreHtml = $this->load->view(
'render_thing',
array(
'someParam' => 'blablabla'
),
true
);
However, in the render_thing view code, on the second call, $permissionMode is still 'guest'. Can you tell me what is going on here?
Thanks!!!
From Loader.php, Loader::_ci_load in the CodeIgniter source...
/*
* Extract and cache variables
*
* You can either set variables using the dedicated $this->load_vars()
* function or via the second parameter of this function. We'll merge
* the two types and cache them so that views that are embedded within
* other views can have access to these variables.
*/
if (is_array($_ci_vars))
{
$this->_ci_cached_vars = array_merge($this->_ci_cached_vars, $_ci_vars);
}
extract($this->_ci_cached_vars);
So, this would be why the parameter is still set. load_vars is not a method, but vars is; problem is that it doesn't provide a facility to erase the cache. Therefore, since CodeIgniter is still PHP4 compatible, you may always do this: $this->load->_ci_cached_vars = array();.
I've had the same problem and figured out the problem in Loader.php as follows;
/*
* Extract and cache variables
*
* You can either set variables using the dedicated $this->load_vars()
* function or via the second parameter of this function. We'll merge
* the two types and cache them so that views that are embedded within
* other views can have access to these variables.
*/
if (is_array($_ci_vars))
{
$this->_ci_cached_vars = array_merge($this->_ci_cached_vars, $_ci_vars);
}else{
$this->load->_ci_cached_vars = array();
}
extract($this->_ci_cached_vars);

Categories