ModX Revolution Caching Dynamicly Generated Placeholders - php

How can I cache (using ModX's cacheManager), the dynamic placeholders i am generating here:
// recursive function to generate our un-ordered list of menu items
if(!function_exists('GenerateMenu')){
function GenerateMenu($level, $parent = 0, $wc, $wi, $we, $cs, $sc, $cl){
try {
$lvl = ++$level;
global $modx;
// Check for #1, should this be cached, #2 does it already exist in the cache
$cached = $modx->cacheManager->get('Navigator');
if($sc && isset($cached)){
// need to get the placeholders from cache - here somehow!
return $cached;
}else{
// get the site start
$siteStartId = $modx->getOption('site_start');
// Set our initial rows array
$rows = array();
// Run our query to get our menu items
$sql = 'Select `id`, `menutitle`, `uri`, `longtitle`, `parent`, `link_attributes`, `class_key`, `content`, `alias`, `introtext`
From `' . $modx->getOption(xPDO::OPT_TABLE_PREFIX) . 'site_content`
Where `deleted` = 0 AND `hidemenu` = 0 AND `published` = 1 AND `parent` = :parent
Order by `parent`, `menuindex`';
$query = new xPDOCriteria($modx, $sql, array(':parent' => $parent));
if ($query->stmt && $query->stmt->execute()) {
$rows = $query->stmt->fetchAll(PDO::FETCH_ASSOC);
}
// do some cleanup
unset($query, $sql);
// make sure we have some rows, and then build our html for the menu
if($rows){
// grab a count of our results
$rCt = count($rows);
$cls = ($lvl > 1) ? 'sub-item-' . $lvl : 'main-item-' . $lvl;
$ret .= ' <ul class="' . $cls . '" id="' . $cls . '-' . $parent . '">' . "\r\n";
for($i = 0; $i < $rCt; ++$i){
// if this resource is a WebLink, show the content in it, as the URL for the href tag, otherwise, use the resource's URI
$url = ($rows[$i]['class_key'] == 'modWebLink') ? $rows[$i]['content'] : '/' . $rows[$i]['uri'];
// Check for the site's start id, if true, show a "blank" link, otherwise show the $url
$showUrl = ($siteStartId == $rows[$i]['id']) ? '/' : $url;
$la = (strlen($rows[$i]['link_attributes']) > 0) ? ' ' . $rows[$i]['link_attributes'] : null;
// Set some dynamic placeholders, they can only be used ont he pages that contain this snippet
$modx->toPlaceholders(array('Title-' . $rows[$i]['id'] => $rows[$i]['longtitle'],
'MenuTitle-' . $rows[$i]['id'] => $rows[$i]['menutitle'],
'URL-' . $rows[$i]['id'] => $showUrl),
'link');
$ret .= ' <li class="' . $cls . '" id="' . $rows[$i]['alias'] . '">' . "\r\n";
$ret .= ' <a href="' . $showUrl . '" title="' . $rows[$i]['longtitle'] . '"' . $la . '>' . $rows[$i]['menutitle'] . '</a>' . "\r\n";
$ret .= GenerateMenu($lvl, $rows[$i]['id']);
// Check for a snippet, and render it
$it = $rows[$i]['introtext'];
if($cs && IsSnippet($it)){
// if we find a snippet in the Summary field, run it, and attach it to our output
preg_match('/\[\[!?(.*)\]\]/', $it, $sm);
$ret .= $modx->runSnippet($sm[1]);
// clean up
unset($sm);
}
$ret .= ' </li>' . "\r\n";
}
$ret .= ' </ul>' . "\r\n";
}
// clean up
unset($rows);
// Check to see if we should cache it, if so, set it to cache, and apply the length of time it should be cached for: defaults to 2 hours
if($sc){
// NEED TO SET THE PLACEHOLDERS TO CACHE SOMEHOW
$modx->cacheManager->set('Navigator', $ret, $cl);
}
// return the menu
return $ret;
}
} catch(Exception $e) {
// If there was an error, make sure to write it out to the modX Error Log
$modx->log(modX::LOG_LEVEL_ERROR, '[Navigator] Error: ' . $e->getMessage());
return null;
}
}
}

The easiest solution may be pdoTools which allows you to establish caching at run time.
http://www.shawnwilkerson.com/modx/tags/pdotools/
Also, I do not believe resource placeholders are cached which is the best place to have your items cahced:
case '+':
$tagName= substr($tagName, 1 + $tokenOffset);
$element= new modPlaceholderTag($this->modx);
$element->set('name', $tagName);
$element->setTag($outerTag);
$elementOutput= $element->process($tagPropString);
break;
From lines 455-461 of https://github.com/modxcms/revolution/blob/master/core/model/modx/modparser.class.php#L455
You may notice the other tag types have:
$element->setCacheable($cacheable);
I cover the parser in Appendix D of my book. I found some issues in it in 2011 which Jason corrected.

Move toPlaceholders() to the end of your script and instead cache the array of placeholder data:
// attempt to retrieve placeholders from cache
$placeholders = $modx->cacheManager->get('Navigator');
// if not in cache, run your snippet logic and generate the data
if (empty($placeholders))) {
$placeholders = array(
'myplaceholder' => 'placeholder data'
);
$modx->cacheManager->set('Navigator', $placeholders, $cl);
}
// set placeholders for use by MODX
$modx->toPlaceholders($placeholders);

Related

Pagination with google ads api in php

I am trying to do the pagination with google-ads-php at the bottom of my page in php, so I get through my ads, like PREVIOUS 1,2,3 NEXT
So this is my code:
public function runExample(GoogleAdsClient $googleAdsClient, int $customerId)
{
$googleAdsServiceClient = $googleAdsClient->getGoogleAdsServiceClient();
// Creates a query that retrieves all ads.
$query = "SELECT campaign.name, ad_group.name, "
. "ad_group_ad.ad.responsive_display_ad.marketing_images, "
. "ad_group_ad.ad.app_ad.images, ad_group_ad.ad.app_ad.youtube_videos, "
. "ad_group_ad.ad.responsive_display_ad.youtube_videos, ad_group_ad.ad.local_ad.videos, "
. "ad_group_ad.ad.video_responsive_ad.videos, ad_group_ad.ad.video_ad.media_file, "
. "ad_group_ad.ad.app_engagement_ad.images, ad_group_ad.ad.app_engagement_ad.videos, "
. "ad_group_ad.ad.display_upload_ad.media_bundle, ad_group_ad.ad.gmail_ad.product_images, "
. "ad_group_ad.ad.gmail_ad.product_videos, ad_group_ad.ad.gmail_ad.teaser.logo_image, "
. "ad_group_ad.ad.image_ad.image_url, ad_group_ad.ad.legacy_responsive_display_ad.square_marketing_image, "
. "ad_group_ad.ad.local_ad.marketing_images, ad_group_ad.ad.responsive_display_ad.logo_images, "
. "ad_group_ad.ad.responsive_display_ad.square_logo_images, "
. "ad_group_ad.ad.responsive_display_ad.square_marketing_images, "
. "ad_group_ad.ad.responsive_display_ad.youtube_videos, "
. "metrics.impressions, campaign.campaign_budget, campaign.status, "
. "campaign.start_date, campaign.end_date, metrics.all_conversions, "
. "metrics.average_cost, ad_group_ad.ad.type, ad_group_ad.ad.id, "
. "campaign.campaign_budget, metrics.cost_micros, ad_group_ad.status, metrics.impressions "
. "FROM ad_group_ad "
. "WHERE segments.date >= '{$this->from}' AND segments.date <= '{$this->to}' "
. "ORDER BY campaign.name ASC";
// Issues a search stream request.
/** #var GoogleAdsServerStreamDecorator $stream */
$stream = $googleAdsServiceClient->search($customerId, $query, ['pageSize' => 10]);
$ads = [];
foreach ($stream->iterateAllElements() as $googleAdsRow) {
dump($googleAdsRow->serializeToJsonString());
/** #var GoogleAdsRow $googleAdsRow */
$ads[] = json_decode($googleAdsRow->serializeToJsonString(), true);
}
As you see the pageSize is set to 10, so it will be 23 pages, because I have 230 ads.
How can I do the pagination, now the $stream returns all ads in one response. How can return only 10 ads, and then when user click for example second page button, it will return the next 10 ads, and so on?
Thanks in advance!
here is how it can be done in Python though -
import sys
from google.ads.googleads.client import GoogleAdsClient
from google.ads.googleads.errors import GoogleAdsException
client = GoogleAdsClient.load_from_storage()
ga_service = client.get_service("GoogleAdsService")
search_request = client.get_type("SearchGoogleAdsRequest")
try:
search_request.query = QUERY_STRING
search_request.customer_id = CUSOMER_ID
search_request.page_size = PAGE_SIZE
df = pd.DataFrame()
while True:
response = ga_service.search(search_request)
dictobj = MessageToDict(response._pb)
df = df.append(pd.json_normalize(dictobj,record_path=['results']))
if response.next_page_token == '':
break
else:
search_request.page_token = response.next_page_token
except GoogleAdsException as ex:
print(ex)
To achieve pagination, you may slightly change the display.
Say, if you want to display 10 records per page, so for the 2nd page, it will be
records 11 to 20.
So , to display page 2, you may revising the part :
foreach ($stream->iterateAllElements() as $googleAdsRow) {
dump($googleAdsRow->serializeToJsonString());
/** #var GoogleAdsRow $googleAdsRow */
$ads[] = json_decode($googleAdsRow->serializeToJsonString(), true);
}
to
$index=0;
foreach ($stream->iterateAllElements() as $googleAdsRow) {
$index++;
if ($index >=11 && $index <=20) {
dump($googleAdsRow->serializeToJsonString());
/** #var GoogleAdsRow $googleAdsRow */
$ads[] = json_decode($googleAdsRow->serializeToJsonString(), true);
}
}
Please see whether the above works, and if so, you may amend the codes to show the data according to page number
Google Ads API provides native pagination that as of now is not very well documented. However, you can only paginate by "next" and "previous" pages.
Here is a working example on keywords since that's the use case most people probably get stuck upon.
$googleAdsServiceClient = $googleAdsClient->getGoogleAdsServiceClient();
$query =
'SELECT ad_group.id, '
. 'ad_group_criterion.type, '
. 'ad_group_criterion.criterion_id, '
. 'ad_group_criterion.keyword.text, '
. 'ad_group_criterion.keyword.match_type '
. 'FROM ad_group_criterion '
. 'WHERE ad_group_criterion.type = KEYWORD';
// Get the stream
$stream = $googleAdsServiceClient->search($customerId, $query, ['pageSize' => 1000]);
// Get the first page
$page = $stream->getPage();
foreach ($page->getIterator() as $googleAdsRow) {
// Iterating over the first page of 1000 keywords.
}
// Get the next page token
$nextPageToken = $page->getNextPageToken();
// Get the second page
$page = $stream->getPage();
$stream = $googleAdsServiceClient->search($customerId, $query, ['pageSize' => 1000, 'pageToken' => $nextPageToken]);
foreach ($page->getIterator() as $googleAdsRow) {
// Iterating over the second page of 1000 keywords.
}
Note that you have to use search() and not searchStream() and iterator of the actual page instead of iterateAllElements()

Alphabetize Petfinder API List

I'm using the Petfinder API for Wordpress plugin. The plugin defaults to listing animals based on how old the Petfinder entries are, from oldest to newest. I'm trying to figure out a way to either do newest to oldest, or alphabetize based on animal names.
The data is loaded via the following code:
function get_petfinder_data($api_key, $shelter_id, $count, $pet = '') {
// If no specific pet is specified
if ( $pet == '' ) {
// Create request URL for all pets from the shelter
$request_url = 'http://api.petfinder.com/shelter.getPets?key=' . $api_key . '&count=' . $count . '&id=' . $shelter_id . '&status=A&output=full';
}
// If a specific pet IS specified
else {
// Create a request URL for that specific pet's data
$request_url = 'http://api.petfinder.com/pet.get?key=' . $api_key . '&id=' . $pet;
}
// Request data from Petfinder
$petfinder_data = #simplexml_load_file( $request_url );
// If data not available, don't display errors on page
if ($petfinder_data === false) {}
return $petfinder_data;
And the code that creates the list looks like this:
function get_all_pets($pets) {
foreach( $pets as $pet ) {
// Define Variables
$pet_name = get_pet_name($pet->name);
$pet_type = get_pet_type($pet->animal);
$pet_size = get_pet_size($pet->size);
$pet_age = get_pet_age($pet->age);
$pet_gender = get_pet_gender($pet->sex);
$pet_options = get_pet_options_list($pet);
$pet_description = get_pet_description($pet->description);
$pet_photo_thumbnail = get_pet_photos($pet, 'medium');
$pet_photo_all = get_pet_photos ($pet, 'large', false);
$pet_more_url = get_site_url() . '/adopt/adoptable-dogs/?view=pet-details&id=' . $pet->id;
$pet_pf_url = 'http://www.petfinder.com/petdetail/' . $pet->id;
// Create breed classes
$pet_breeds_condensed = '';
foreach( $pet->breeds->breed as $breed ) {
$pet_breeds_condensed .= pet_value_condensed($breed) . ' ';
}
// Create options classes
$pet_options_condensed = '';
foreach( $pet->options->option as $option ) {
$option = get_pet_option($option);
if ( $option != '' ) {
$pet_options_condensed .= pet_value_condensed($option) . ' ';
}
}
// Compile pet info
// Add $pet_options and $pet_breeds as classes and meta info
$pet_list .= '<div class="vc_col-sm-3 petfinder ' . pet_value_condensed($pet_age) . ' ' . pet_value_condensed($pet_gender) . ' ' . $pet_breeds_condensed . ' ' . $pet_options_condensed . '">' .
'<div class="dogthumbnail">' .
'' . $pet_photo_thumbnail . '<br>' .
'</div>' .
'<a class="dogname" href="' . $pet_more_url . '">' . $pet_name . '</a><br>' .
'<span> ' . $pet_age . ' • ' . $pet_gender . '<br>' .
'<div class="dogbreed">' . $pet_breeds_condensed . '</div>' .
'<a class="morelink" href="' . $pet_more_url . '">Learn More <i class="fas fa-angle-right"></i></a><br>' .
'</div>';
}
// Return pet list
return $pet_list;
Here's an example of the XML that the Petfinder API spits out (right now there are 25 pet entries in the full thing):
<petfinder xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="http://api.petfinder.com/schemas/0.9/petfinder.xsd">
<header>
<version>0.1</version>
<timestamp>2018-06-11T17:32:34Z</timestamp>
<status>
<code>100</code>
<message/>
</status>
</header>
<lastOffset>25</lastOffset>
<pets>
<pet>
<id>31035385</id>
<shelterId>IL687</shelterId>
<shelterPetId/>
<name>Chanel</name>
<animal>Dog</animal>
<breeds>...</breeds>
<mix>yes</mix>
<age>Adult</age>
<sex>F</sex>
<size>M</size>
<options>...</options>
<description>...</description>
<lastUpdate>2014-12-14T17:59:49Z</lastUpdate>
<status>A</status>
<media>...</media>
<contact>...</contact>
</pet>
</pets>
</petfinder>
I'd like to sort all entries by either "name" or "lastUpdate". I've been looking at a lot of posts about sorting XML element objects but they either don't seem to work or I can't figure out how to apply them specifically to my code. I'm not super well-versed in this stuff, so any assistance is much appreciated!!
After a LOT of research and trial and error, I figured out how to organize alphabetically by animal name. Posting this in case anybody is ever trying to figure out the same.
First of all, I was wrong in my assumption of which sections I might need to be editing. It was actually line 723 of the plugin file. Here's how I modified the code for that section:
// Display a list of all available dogs
else {
// Access Petfinder Data
$petfinder_data = get_petfinder_data($api_key, $shelter_id, $count);
// If the API returns without errors
if( $petfinder_data->header->status->code == '100' ) {
// If there is at least one animal
if( count( $petfinder_data->pets->pet ) > 0 ) {
//Sort list of dogs ALPHABETICALLY by NAME
$petSXE = $petfinder_data->pets->children();
$petArray = array();
foreach($petSXE->pet as $d) {
$petArray[] = $d;
}
function name_cmp($a, $b) {
$va = (string) $a->name;
$vb = (string) $b->name;
if ($va===$vb) {
return 0;
}
return ($va<$vb) ? -1 : 1;
}
usort($petArray, 'name_cmp');
$pets = $petArray;
// Compile information that you want to include
$petfinder_list = get_type_list($pets).
get_age_list($pets) .
get_size_list($pets) .
get_gender_list($pets) .
get_options_list($pets) .
get_breed_list($pets) .
get_all_pets($pets);
}
This is adapting the solution I found in this thread: sort xml div by child node PHP SimpleXML

laravel mysql to query builder

i'm relatively new to laravel and i'm having issues when trying to convert this function to laravel's query builder. This is the function i've been given which also runs a python script to decrypt the database.
Using the documentation from laravel.com you can do something like this:
function call($unitId)
{
$pfContact = DB::table('PFContact')
->where('UnitID', $unitId)
->latest() // Order by created_at
->first([ // Only retrieve these columns
'Send',
'Receive',
'Core',
'lock'
]);
$pfReadings = DB::table('PFReadings')
->get();
$rowCount = $pfReadings->count();
foreach ($pfReadings as $i => $reading) {
echo $i < count($reading) / $rowCount;
foreach ($reading as $column => $value) {
echo shell_exec(
'python3 enc.py ' . $value
. ' ' . $pfContact->Send
. ' ' . $pfContact->Receive
. ' ' . $pfContact->Core
. ' ' . $pfContact->lock . ' '
. $unitId . ' l'
) . '~';
}
echo ';';
}
}
And although I do not know what arguments this pyhton script needs you should really think this through. Why would you use PHP for this and not just handle everything from the python (or php) side because this looks over complicated to me.

how i get the information

I have this script and i need this result:
Numelem / Title / ElmPoste
foreach ( $jobPrintingInfos as $jobprinting_index => $jobprinting ) {
$machine = $jobprinting ['ElmPoste'];
//var_dump($machine);
// var_dump($machine);
}
foreach ( $GetJobResult->Components->Component as $component_index => $component ) {
$quote_support ='';
$quote_impression = '';
$quote_title = ($component->NumElem) . ' / ' . $component->Title . ' ' .$machine. "\r\n";
var_dump($quote_title);
}
but when i do var_dump($quote_title) i have the last machine not all the machine :such as
1/Dessus /Nesspresso
2/Inter/Nesspresso
3/Assem/Nesspresso
Thanks in advance
i dont know the idea behind that script... but if you want that $quote_title <-- is one long String then you need to add a .
like this:
$quote_title .= ($component->NumElem) . ' / ' . $component->Title . ' ' .$machine. "\r\n";
--------------------^
or as array, then its easyer to use a for loop

PayOne interface not accepting my HASH value

I have been trying to get the PayOne FrontEnd interface to accept the hash value from my request, to absolutely no avail. I have a support ticket open but need a solution relatively quick, so here I am.
The error returned is "Hashwert Nicht Korrekt" (Hash value incorrect).
Here is my code:
$request="authorization";
$portalid = 2017373;
$aid = 24413;
$key = "secretkeychangedforsecuritoyreasons"; // Key (configurable in the payment portal)
$id[1]= "PART_100";
$pr[1]= 2000;
$no[1] = 1;
$de[1] = "Registration Fee";
$va[1] = 19;
$amount = round($pr[1]*$no[1]);
$clearingtype = "cc";
$mode = "test";
$currency="EUR";
$reference="24393";
$customerid="24393";
$hash = md5(
$aid .
$amount .
$currency .
$customerid .
$clearingtype .
$de[1] .
$id[1] .
$mode .
$no[1] .
$portalid .
$pr[1] .
$reference .
$request .
$va[1] .
$key
);
$url="https://secure.pay1.de/frontend/?request=" . $request .
"&aid=" . $aid .
"&mode=" . $mode .
"&clearingtype=" . $clearingtype .
"&portalid=" . $portalid .
"&customerid=" . $customerid .
"&currency=" . $currency .
"&amount=" . $amount .
"&reference=" . $reference .
"&id[1]=" . $id[1] .
"&pr[1]=" . $pr[1] .
"&no[1]=" . $no[1] .
"&de[1]=" . $de[1] .
"&va[1]=" . $va[1] .
"&hash=" . $hash;
header("Location: $url");
I have checked and re checked the docs and can find no errors in the way I am puttign it together. If I change single values like portalid, etc. it throws the appropriate error.
Any help would be appreciated.
I found the following section in the client-api-documentation:
Attention:
PAYONE Platform expects the calculated
hash
value
converted
to
lower
case;
e.g.
87dbc7c369b85b7a699adff1a2b27bab
Maybe you have some capital letter in your hash? I do an ".toLowerCase()" (in Java) on the encrypted hash.
One other option: you forgot some parameters.
on the first look i can't see the following: mid
We use the following:
$req['aid'] = 09876; // Sub-AccountID
$req['portalid'] = 6789012; // portal-ID
$req['mode'] = "live"; // Live || test
$req['request'] = "authorization"; // Request
$req['id[1]'] = $YourProductID; // e.g. articleno
$req['pr[1]'] = $singleprice;
$req['no[1]'] = $count; // count or pieces
$req['de[1]'] = $articledescription;
$req['amount'] = $summary; // price summary
$req['currency'] = "EUR";
$req['reference'] = $unique_ref.$YourProductId; //my unique is time()
$req['customerid'] = $userId;
$req['clearingtype'] = $clearing; // cc || wlt
$req['encoding'] = "UTF-8";
$req['targetwindow'] = "top";
ksort($req); //so i know its important to sort
//building the hash
foreach ($req as $key => $val) {
$req['hash'] = $req['hash'] . $val;
}
// all in md5() ... note your own payment ID
$req['hash'] = md5($req['hash'] . $YourPayOneID);
Hope it helps ;)

Categories