We built a site with Concrete5 that was originally developed in Joomla. Our job was to bring over everything and Concrete5-ize it. A major part of this site is about 1200 audio teachings, with each teaching having various attributes, such as topic, author, program, location, etc.
Some teachings might have more than one attribute assigned, say multiple keywords or topics.
I would like to give counts to all of the attributes so that the visitor can see how many teachings are by a certain author, or how many are on a particular topic at a glance, ie:
Ethics (20)
Fear (42)
Gratitude (55)
My original code turned out to have way too much overheard to be practical for so many teachings and so many attributes. Basically, I ran through and for each attribute, I did a lookup for the total count based on the PageList count. We're talking hundreds of lookups with each page load. Turning on cache didn't seem to help here.
Are there any other strategies that have proved successful for aggregating counts for attributes over a large-ish number of pages?
Here is the site for reference: http://everydayzen.org/teachings/
I typically say "don't access the database directly; use the API", but I think you should be using the DB here.
Check out the [Collection|File]SearchIndexAttributes table. (I'm not sure if teachings are files or pages. If pages, you'll need to reindex them regularly, via the job in the dashboard.) Looking at the index table will be a lot easier than joining in the most-recent version in the attribute value table. Once you see that table, you can do some simple GROUPing within SQL.
If you want to use the API, you could do it as you do today as a batch, do the appropriate calculations, and then cache it.
There's no reason caching shouldn't work but the first hit (when the cache is cold) will, of course, take the full amount of time. You should cache my IndexAttributes idea (a full table read and looping isn't trivial), but at least with a cold cache that should take a fraction of a second vs 10 or more seconds that hundreds of page list calls could take.
I've done something similar on a job site for Concrete5, by showing the counts of each Department that jobs fall in.
i.e. HR (32), Sales (12) and so on
This is the code taken from a Helper that acheives this (this is just the related functions included):
<?php
class JobHelper {
/**
* GetDepartmentJobsCount
* Returns array of Department names with job count based on input Pages
* #param Array(Pages) - Result of a PageList->getPages
* #return Array
*/
public function getDepartmentJobsCount($pages) {
$depts = $this->getDepartments();
$cj = $this->setCounts($depts);
$cj = $this->setAttributeCounts($cj, $pages,'job_department');
return $cj;
}
/**
* GetDepartments
* Return all available Departments
* #return Array(Page)
*/
public function getDepartmentPages(){
$pld = new PageList();
$pld->filterByPath('/working-lv'); //the path that your Teachings all sit under
$pld->setItemsPerPage(0);
$res = $this->getPage();
$depts = array();
foreach($res as $jp){
$depts[$jp->getCollectionName()] = $jp;
}
ksort($depts);
return $depts;
}
/**
* PopulateCounts
* Returns array of page names and counts
* #param Array - Array to feed from
* #return Array
*/
public function setCounts($v){
foreach($v as $w){
$a[$w]['count'] = 0;
}
return $a;
}
/**
* PopulateCounts
* Returns array of page names, with counts added from attribute, and paths
* #param Array - Array to add counts and paths in to
* #param Array(Pages) - Pages to run through
* #param String - Attribute to also add to counts
* #param String - Optional - Job Search parameter, leave blank to get Page URL
* #return Array
*/
public function setAttributeCounts($cj, $pages, $attr){
foreach($pages as $p) {
$pLoc = explode('|',$p->getAttribute($attr)); // Our pages could have multiple departments pipe separated
foreach($pLoc as $locName){
$cj[$locName]['count']++;
}
}
return $cj;
}
You can then do the following from a PageList template
$jh = Loader::helper('job');
$deptCounts = $jh->getDepartmentJobsCount($pages);
foreach($deptCounts as $dept => $data) {
echo $dept . '(' . $data['count] . ')';
}
Related
I need to create 2 items, use get method to check if everything is ok and after that I should delete these items.
I have 1 tc - getItem, which uses 2 helpers (postItem and deleteItem).
For getItem I need to have itemId, which I get from postItem, where this variable is defined. After that I use the same itemId for deleteItem as afterhook. What I do:
Feature:get item
Background:Pre-conditions
* url apiUrl
* call read('classpath:/helpers/features/postItem.feature')
* configure afterScenario = function(){karate.call('classpath:/helpers/features/deleteItem.feature')}
Scenario: Get items
* path '/items/'
And param id = itemId
When method Get
Then status 200
It works but I create only 1 item and delete it correctly because itemId is predefined in postItem and I`m able to re-use it. I saw how to use karate.repeat from HERE but when I do the next
* def item = function(i){ return karate.call ('classpath:/helpers/features/postItem.feature')}
I`m not able to get itemId and as a result not able to delete it. Have tried to use
* print item.response
but it is "null"
So I have 2 questions:
How to get variable from postItem
How to delete each of these created items using afterHook?
May I offer some advice. I would NOT try to create helpers and re-use them like this. Please take some time to read this, and then you may understand: https://stackoverflow.com/a/54126724/143475
I would definitely not use a hook also. Please think of the people who need to maintain your test in the future, they will cry.
Here is how I would write your test. And let me repeat, it is OK to repeat some code when your are doing test automation. For a real, working example, see here.
Background:
* url apiUrl + '/items'
Scenario:
* request {}
* method post
* status 201
* path response.id
* method get
* status 200
* request {}
* method post
* status 201
* path response.id
* method delete
# and so on
Otherwise, the only thing I will say is please refer to the documents on how you can call features and get data back in a loop without using karate.repeat() which should be used only for creating JSON arrays. You can see this answer which has an example and links to the documentation: https://stackoverflow.com/a/75394445/143475
Have found solution how can I do this using DRY pattern + afterhooks.
Feature:get items
Background:Pre-conditions
* url apiUrl
* def item = function(i){ return karate.call ('classpath:/helpers/features/postItem.feature')}
* def createdItem = karate.repeat(2, item )
* table createdItems
|itemId |
|createdItem[0].response.data.id|
|createdItem[1].response.data.id|
* configure afterScenario = function(){karate.call('classpath:/helpers/features/deleteItem.feature', createdItems )}
Scenario: Get all items
* path '/items'
When method Get
Then status 200
It works, but maybe it also can be updated. Im new in this)
So, basically, what I do:
I create 2 items, for get method using karate.repeat with calling postItem feature
I create table with itemId references
Create afterHook with calling deleteItem.feature, which should have argument itemId and I provide created table for this.
And I have scenario, which checks created items
And after that these created items are deleted by afterhooks.
As a result, I have clear scenario, which contains
Pre-conditions --> creating items (preparing data)
Scenario body --> GET method
Post-conditions --> deleting created items and returning to default state.
All of this I do because dont have DB read permission) In an ideal world, preparing data should be done via SQL and deleted as well)
Hope this will help someone)) Also, if you find better solution, feel free to write this here) Tnx
How to get specific content type document count in alfresco share UI using Lucene query?
I have tried to query in alfresco share UI Alfresco Lucene query. but it's only giving first 100 results.
Is there any best way to get only document count by specific content type or document count under specific alfresco site??
Please suggest if there is any other best and useful way.
Thanks in Advance.
The class PatchDAO has a method that returns the number of node with a given type:
/**
* Gets the total number of nodes which match the given Type QName.
*
* #param typeQName the qname to search for
* #return count of nodes that match the typeQName
*/
public long getCountNodesWithTypId(QName typeQName);
where typeQName is, of course, the QName of the type.
This method should return the total count and should be the most efficient.
UPDATE:
If you need the count on a specific site this method is not actually usable.
ResultSet result = searchService.query(, SearchService.LANGUAGE_LUCENE, "+PATH:\"/app:company_home/cm:" + + "/*\"" + " +TYPE:\"" + + "\"" );
You can change the parameters as per your need.
Thanks,
Kintu
Hitting the database directly is a very bad idea, so don't even start getting into that bad habit.
Using the Alfresco foundational Java API would require that the Java class be deployed to the server, which is a pain.
The easiest way to do this is to use OpenCMIS. You can run OpenCMIS code remotely, and you can use its paging result set to page through the query results, see Apache CMIS: Paging query result
First the introduction, in case there's is a better approach: I have a product table with *product_id* and stock, where stock can be as big as 5000 or 10000, I need to create a list (in another table) where I have a row for each item, this is, if a *propduct_id* has stock 1000 I'll have 1000 rows with this *product_id*, and plus, this list needs to be random.
I chose a PHP (symfony2) solution, as I found how to get a random single product_id based on stock or even how to random order the product list, but I didn't find how to "multiply" this rows by stock.
Now, the main problem:
So, in PHP it's no so difficult, get product_id list, "multiply" by stock and shuffle, the problem comes when I want to save:
If I use $em->flush every 100 records or more I get a memory overflow after a while
If I use $em->flush in every record it takes ages to save
This is my code to save which maybe you can improve:
foreach ($huge_random_list as $indice => $id_product)
{
$preasignacion = new ListaPreasignacion();
$preasignacion->setProductId($id_product);
$preasignacion->setOrden($indice+1);
$em->persist($preasignacion);
if ($indice % 100 == 0) $em->flush();
}
$em->flush();
Edit with final solution based on #Pazi suggestion:
$conn = $em->getConnection();
foreach ($huge_random_list as $indice => $id_product)
{
$conn->executeUpdate("insert into product_list(product_id, order) "
." values({$id_product}, {$indice})");
}
I would suggest to abstain from doctrine ORM and use the DBAL connection an pure sql queries for this purpose. I do this always in my applications, where I have to store much data in short time. Doctrine adds too much overhead with objects, checks and dehydrating. You can retrieve the DBAL connection via the DI container. For example in a contoller:
conn = $this->get('database_connection');
Read more about DBAL
I'm trying to write a PHP code to validate a form input in a field. If the field has already value the system must send an error message. If there is not a value or the value is the same like the input then the form can be submitted.
The edited code is:
/**
* Implement a function to get the ID and the title of the referenced node
* of type Reservation
* by the nodereference field called Period
* in the currently edited node from type Board
* Try to do this by the node_load() instead of the database query
* Is it the correct method to get the edited node's ID?
**/
function period_get_value() {
$thisnodeboard = $node->field_period_1[$node->language][0]['nid'];
$reservationrec = node_load(array('nid'=>$thisnodeboard));
return $reservationrec->title;
}
/**
* Implement the hook_form_FORM_ID_alter function to validate
* if the field Period has already value set
* and if there is such to check if it is the same as the input value
**/
function period_validate_form_slickgrid_editor_form_alter(&$form, $form_state){
/**
* The current value is the title of the referenced node
**/
$valcurr = period_get_value();
$valnew = $form_state['values']['field_period_1'];
if (isset($valcurr)&&($valcurr!=$valnew)){
form_set_error('field_period_1', t('There is already value set for this field'));
}
return $form;
}
But it still doesn't work - does not set any message and allow for changing the existing value in the field_period_1.
Firstly, writing a manual SQL query in D7 is an absolute last resort.
OK so you actually want to just prevent the user from updating a field after the node has been created.
You can do one of two things. If you only want to prevent edits from the node/edit form you could implement hook_form_FORM_ID_alter() and then add your own validate or submit handler. You would then validate that the field has not changed and act accordingly.
If you wanted to prevent it happening from anywhere, Eg programmatically. You could implement hook_node_update() and check $node->is_new and $node->type to prevent changes to nodes that are not new.
I am missing the SQL out of this to Bulk update attributes by SKU/UPC.
Running EE1.10 FYI
I have all the rest of the code working but I"m not sure the who/what/why of
actually updating our attributes, and haven't been able to find them, my logic
is
Open a CSV and grab all skus and associated attrib into a 2d array
Parse the SKU into an entity_id
Take the entity_id and the attribute and run updates until finished
Take the rest of the day of since its Friday
Here's my (almost finished) code, I would GREATLY appreciate some help.
/**
* FUNCTION: updateAttrib
*
* REQS: $db_magento
* Session resource
*
* REQS: entity_id
* Product entity value
*
* REQS: $attrib
* Attribute to alter
*
*/
See my response for working production code. Hope this helps someone in the Magento community.
While this may technically work, the code you have written is just about the last way you should do this.
In Magento, you really should be using the models provided by the code and not write database queries on your own.
In your case, if you need to update attributes for 1 or many products, there is a way for you to do that very quickly (and pretty safely).
If you look in: /app/code/core/Mage/Adminhtml/controllers/Catalog/Product/Action/AttributeController.php you will find that this controller is dedicated to updating multiple products quickly.
If you look in the saveAction() function you will find the following line of code:
Mage::getSingleton('catalog/product_action')
->updateAttributes($this->_getHelper()->getProductIds(), $attributesData, $storeId);
This code is responsible for updating all the product IDs you want, only the changed attributes for any single store at a time.
The first parameter is basically an array of Product IDs. If you only want to update a single product, just put it in an array.
The second parameter is an array that contains the attributes you want to update for the given products. For example if you wanted to update price to $10 and weight to 5, you would pass the following array:
array('price' => 10.00, 'weight' => 5)
Then finally, the third and final attribute is the store ID you want these updates to happen to. Most likely this number will either be 1 or 0.
I would play around with this function call and use this instead of writing and maintaining your own database queries.
General Update Query will be like:
UPDATE
catalog_product_entity_[backend_type] cpex
SET
cpex.value = ?
WHERE cpex.attribute_id = ?
AND cpex.entity_id = ?
In order to find the [backend_type] associated with the attribute:
SELECT
backend_type
FROM
eav_attribute
WHERE entity_type_id =
(SELECT
entity_type_id
FROM
eav_entity_type
WHERE entity_type_code = 'catalog_product')
AND attribute_id = ?
You can get more info from the following blog article:
http://www.blog.magepsycho.com/magento-eav-structure-role-of-eav_attributes-backend_type-field/
Hope this helps you.