I recently received an emailer from Onnit Labs that included a Countdown Module Timer inside the emailer using a gif image. The emailer can be viewed here: https://www.onnit.com/emails/lastchance-historic/
The Image can be seen here:
I looked into it, and it seems you can keep sending new frames to an animated GIF using gifsockets, as a GIF doesn't specify how many frames it has when loaded in the browser. Here it is on github: http://github.com/videlalvaro/gifsockets
I thought this was pretty interesting and a cool effect indeed. Does anyone have any other insights on how this could be accomplished? It seems as though the one they're using at Onnit seems to change the countdown according to date appended at the end of URL or image.
onnit.com/emails/_modules/timer/?end=2012-12-27+00:00:00&dark=1
I'm trying to accomplish the same thing to send in an email, but I am a little stumped.
While maybe gifsockets would work (I haven't tried that before...), there is no network traffic while I am looking at the image other than the initial image load. I am also seeing it it jump from 41 to 42 again. A Reload took it down to 39.
It appears to be just a script that generates 60 frames of animation and sends them to the user. This could probably be done in any language.
Here is how it is done in php:
http://seanja.com/secret/countdown/
I found http://sendtric.com/ which is free and very easy to integrate.
You could try http://makedreamprofits.com/pt/. Instead of supplying additional content to a gif, this countdown is broken into separate images and can count for up to 20 mins without increasing much traffic.
P.S. Gmail is precaching images, so, supplying it endlessly with new frames is not possible.
I really appreciated Sean Ja's answer. (He deserves more upvotes.) And then I wanted to make the code more readable and configurable (and support text on a transparent gif and automatically center the text):
use Carbon\Carbon;
class CountdownGifHelper {
const DELAY = 100; /* Why was this labeled as 'milliseconds' when it seems like a value of 100 here causes 1 frame to be shown per second? */
const MAX_FRAMES = 120;
/**
*
* #param string $bgImg
* #param \DateInterval $interval
* #param array $fontArr
* #param array $frames
* #param array $delays
* #param string $format
*/
public function addFrame($bgImg, $interval, $fontArr, &$frames, &$delays, $format) {
$image = imagecreatefrompng($bgImg); //Each frame needs to start by creating a new image because otherwise the new numbers would draw on top of old ones. Here, it doesn't really matter what the PNG is (other than for size) because it's about to get filled with a new color.
$text = $interval->format($format);
ob_start();
imageSaveAlpha($image, true);
$backgroundColor = $fontArr['backgroundColor'];
imagefill($image, 0, 0, $backgroundColor); //https://stackoverflow.com/a/17016252/470749 was a helpful hint
imagecolortransparent($image, $backgroundColor);
$this->insertCenteredText($image, $fontArr, $text);
//imagettftext($image, $font['size'], $font['angle'], $font['x-offset'], $font['y-offset'], $font['color'], $font['file'], $text);//this was the old way
imagegif($image); //The image format will be GIF87a unless the image has been made transparent with imagecolortransparent(), in which case the image format will be GIF89a.
$frames[] = ob_get_contents();
ob_end_clean();
$delays[] = self::DELAY;
}
/**
*
* #param resource $image
* #param array $fontArray
* #param string $text
*/
public function insertCenteredText(&$image, $fontArray, $text) {
$image_width = imagesx($image);
$image_height = imagesy($image);
$text_box = imagettfbbox($fontArray['size'], $fontArray['angle'], $fontArray['file'], $text); // Get Bounding Box Size
$text_width = $text_box[2] - $text_box[0];
$text_height = $text_box[7] - $text_box[1];
// Calculate coordinates of the text https://stackoverflow.com/a/14517450/470749
$x = ($image_width / 2) - ($text_width / 2);
$y = ($image_height / 2) - ($text_height / 2);
imagettftext($image, $fontArray['size'], $fontArray['angle'], $x, $y, $fontArray['color'], $fontArray['file'], $text);
}
/**
*
* #param int $timestamp
* #param string $bgImg
* #param array $fontArray
* #return string [can be used by Laravel response()->make($gifString, 200, $headers)]
*/
public function getAnimatedGif($timestamp, $bgImg, $fontArray) {
$future_date = Carbon::createFromTimestamp($timestamp);
$time_now = time();
$moment = new \DateTime(date('r', $time_now));
$frames = [];
$delays = [];
for ($i = 0; $i <= self::MAX_FRAMES; $i++) {
$interval = date_diff($future_date, $moment);
if ($future_date < $moment) {
$this->addFrame($bgImg, $interval, $fontArray, $frames, $delays, '00 : 00 : 00');
$loops = 1; //stay stuck on this frame
break;
} else {
$this->addFrame($bgImg, $interval, $fontArray, $frames, $delays, '%H : %I : %S');
$loops = 0; //infinite loop
}
$moment->modify('+1 second');
}
$animatedGif = new \App\Helpers\AnimatedGif($frames, $delays, $loops, 0, 0, 0);
return $animatedGif->getAnimation();
}
/**
* ONEDAY allow config via params
* #param resource $image
* #return array
*/
public function getFontArray($image) {
$fontArr = [
'file' => resource_path('assets/fonts/Kanit-Regular.ttf'),
'size' => 30,
//'x-offset' => 5,
//'y-offset' => 30,
'color' => imagecolorallocate($image, 90, 90, 90), //gray
'backgroundColor' => imagecolorallocate($image, 0, 0, 0), //white. Must match the arguments provided to AnimatedGif (such as 0,0,0).
'angle' => 0,
];
return $fontArr;
}
}
Related
The TSref entry for slide explains:
Up to Version 9 of TYPO3 the sliding stopped when reaching a folder.
Beginning with TYPO3 10 this is not longer the case. See
$cObj->checkPid_badDoktypeList.
Ok, this variable is still 255 (formerly directly, now via constant PageRepository::DOKTYPE_RECYCLER).
What exactly should I see there that will help me? Or better, how to get content sliding still working like before?
You have to extend the ContentObjectRenderer class and overwrite the getSlidePids method with your own extension.
In the boot function of ext_localconf.php:
$GLOBALS['TYPO3_CONF_VARS']['SYS']['Objects'][\TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer::class] = [
'className' => \YourVendor\YourExtensionKey\ContentObject\ContentObjectRenderer::class
];
Then you have to create your own "Classes/ContentObject/ContentObjectRenderer.php" with:
<?php
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
namespace YourVendor\YourExtension\ContentObject;
use TYPO3\CMS\Core\Domain\Repository\PageRepository;
use TYPO3\CMS\Core\Utility\GeneralUtility;
class ContentObjectRenderer extends \TYPO3\CMS\Frontend\ContentObject\ContentObjectRenderer
{
/**
* Returns all parents of the given PID (Page UID) list
*
* #param string $pidList A list of page Content-Element PIDs (Page UIDs) / stdWrap
* #param array $pidConf stdWrap array for the list
* #return string A list of PIDs
* #internal
*/
public function getSlidePids($pidList, $pidConf)
{
// todo: phpstan states that $pidConf always exists and is not nullable. At the moment, this is a false positive
// as null can be passed into this method via $pidConf. As soon as more strict types are used, this isset
// check must be replaced with a more appropriate check like empty or count.
$pidList = isset($pidConf) ? trim($this->stdWrap($pidList, $pidConf)) : trim($pidList);
if ($pidList === '') {
$pidList = 'this';
}
$tsfe = $this->getTypoScriptFrontendController();
$listArr = null;
if (trim($pidList)) {
$listArr = GeneralUtility::intExplode(',', str_replace('this', (string)$tsfe->contentPid, $pidList));
$listArr = $this->checkPidArray($listArr);
}
$pidList = [];
if (is_array($listArr) && !empty($listArr)) {
foreach ($listArr as $uid) {
$page = $tsfe->sys_page->getPage($uid);
if($page['doktype'] == PageRepository::DOKTYPE_SYSFOLDER)
break;
if (!$page['is_siteroot']) {
$pidList[] = $page['pid'];
}
}
}
return implode(',', $pidList);
}
}
I try to use and customize the CTypes of fluid_styled_content as much as possible. Therefore the select-field "Layout" is very useful to have a possibility to select some different styles (like red box, shadow, or image-stuff). But if you have some possibilities to select it is not shown in backend preview an every element is looking the same.
Is there a way to show the selected value the layout field in backend preview for textmedia?
To get this done register a hook (path: yourextension/Classes/Hooks/PageLayoutView/TextMediaCustomPreviewRenderer.php) like that:
<?php
namespace Vendor\Yourextension\Hooks\PageLayoutView;
/*
* This file is part of the TYPO3 CMS project.
*
* It is free software; you can redistribute it and/or modify it under
* the terms of the GNU General Public License, either version 2
* of the License, or any later version.
*
* For the full copyright and license information, please read the
* LICENSE.txt file that was distributed with this source code.
*
* The TYPO3 project - inspiring people to share!
*/
use \TYPO3\CMS\Backend\View\PageLayoutViewDrawItemHookInterface;
use \TYPO3\CMS\Backend\View\PageLayoutView;
/**
* Contains a preview rendering for the page module of CType="textmedia"
*/
class TextMediaCustomPreviewRenderer implements PageLayoutViewDrawItemHookInterface
{
/**
* Preprocesses the preview rendering of a content element of type "Text Media"
*
* #param \TYPO3\CMS\Backend\View\PageLayoutView $parentObject Calling parent object
* #param bool $drawItem Whether to draw the item using the default functionality
* #param string $headerContent Header content
* #param string $itemContent Item content
* #param array $row Record row of tt_content
*
* #return void
*/
public function preProcess(
PageLayoutView &$parentObject,
&$drawItem,
&$headerContent,
&$itemContent,
array &$row
)
{
$pageTs = \TYPO3\CMS\Backend\Utility\BackendUtility::getPagesTSconfig($row['pid']);
if ($row['CType'] === 'textmedia') {
$itemContent .= '<p>Layoutversion: ' . $pageTs['TCEFORM.']['tt_content.']['layout.']['types.']['textmedia.']['addItems.'][$row['layout']] . '</p>';
if ($row['bodytext']) {
$itemContent .= $parentObject->linkEditContent(
$parentObject->renderText($row['bodytext']),
$row
) . '<br />';
}
if ($row['assets']) {
$itemContent .= $parentObject->thumbCode($row, 'tt_content', 'assets') . '<br />';
}
$drawItem = false;
}
}
}
And in your ext_localconf.php you put like that:
// Register for hook to show preview of tt_content element of CType="textmedia" in page module
$GLOBALS['TYPO3_CONF_VARS']['SC_OPTIONS']['cms/layout/class.tx_cms_layout.php']['tt_content_drawItem']['textmedia'] = \Vendor\Yourextension\Hooks\PageLayoutView\TextMediaCustomPreviewRenderer::class;
In my case I offer the different options of the select in pageTsconfig like that:
TCEFORM.tt_content.layout.types.textmedia.addItems {
50 = Textbox grau, Bildergalerie oben
60 = Textbox grau, Bildergalerie unten
110 = Blau, rechtsbündig
210 = Hellblau, linksbündig
220 = Rot, linksbündig
310 = Akkordeon
}
It is the better way to use correct language handling by locallang.xlf for that. If you do it like that maybe you have to change the code example a bit...
This was the result of a thread at "TYPO3 Fragen, Antworten, inoffizielle Gruppe" on Facebook. Thanks a lot to every helping hand specially to Wolfgang Klinger :-)
I am building a Module for the category page in prestashop.
Basically in my module.php I have this code:
$category = new Category(Context::getContext()->shop->getCategory(),(int)Context::getContext()->language->id);
$nb = (int)(Configuration::get('MOD_NBR'));
$products = $category->getProducts((int)Context::getContext()->language->id, 1, ($nb ? $nb : 10));
$this->smarty->assign(array(
'myproducts' => $products,
'add_prod_display' => Configuration::get('PS_ATTRIBUTE_CATEGORY_DISPLAY'),
'homeSize' => Image::getSize(ImageType::getFormatedName('home')),
));
Then in mymodule.tpl I have this:
{foreach from=$products item=product name=myproducts}
+ other stuff
The problem is that I need to get all the products inside the category, but it is only displaying the products on the first page. I can't delete or modify pagination completely, because I need the other products on the category page to be paginated, but in my module I want to get all the products at once (after I will filter them to show only some of them).
As you may see I am kind of lost, but also so desperate, I will appreciate any guidance :)
thanks
In your code you have:
$products = $category->getProducts((int)Context::getContext()->language->id, 1, ($nb ? $nb : 10));
which corresponds to:
/**
* Return current category products
*
* #param integer $id_lang Language ID
* #param integer $p Page number
* #param integer $n Number of products per page
* #param boolean $get_total return the number of results instead of the results themself
* #param boolean $active return only active products
* #param boolean $random active a random filter for returned products
* #param int $random_number_products number of products to return if random is activated
* #param boolean $check_access set to false to return all products (even if customer hasn't access)
* #return mixed Products or number of products
*/
public function getProducts($id_lang, $p, $n, $order_by = null, $order_way = null, $get_total = false, $active = true, $random = false, $random_number_products = 1, $check_access = true, Context $context = null)
So you are asking for page 1 and $nb or 10 elements.
Try adding before that line $nb = 10000; to show up to 10k products (and feel free to increase it if your category has more than 10k products)
So it should be something like:
$category = new Category(Context::getContext()->shop->getCategory(),(int)Context::getContext()->language->id);
$nb = 10000;
$products = $category->getProducts((int)Context::getContext()->language->id, 1, ($nb ? $nb : 10));
$this->smarty->assign(array(
'myproducts' => $products,
'add_prod_display' => Configuration::get('PS_ATTRIBUTE_CATEGORY_DISPLAY'),
'homeSize' => Image::getSize(ImageType::getFormatedName('home')),
));
UPDATE: Reviewing your question I've found that in your template you are iterating $products variable, but assigning it as myproducts. I'm guessing smarty has the assigned variables $products with only the first page and $myproducts with the ones you have obtained.
Try updating your template to:
{foreach from=$myproducts item=product name=myproducts}
I'm using Typo3 4.5.3 and I have image files in a directory from which I want to randomly select one to display on the current page, but I can't seem to get the typoscript right to make listnum = rand work. Here's my extension template:
# Pick a random image to display
temp.banner = IMAGE
temp.banner {
file {
height = 165
width = 954
import {
filelist = {$templatePathPrefix}images/banners | jpg,jpeg,png,gif | name | | 1
listNum = rand
}
}
params = class="bannerPic"
}
If I change the listnum setting to 0, 1, etc. it inserts the corresponding img HTML into the page. Setting it to 'last' also works, but rand always inserts the first image no matter how many times I refresh. Since I can select specific images (0, 1, ...) I know the general setup works, just not the random selection.
I've seen TS for various other uses of rand which wrap the outer object in a COA_INT object, but that didn't work for me either. Did I miss something about where to place the listNum = rand? I'm pretty new at Typo3 so lots of it is still pretty opaque to me at this point (or does it show?). Thanks for any insight you can provide.
mak_stdwrapextended extension adds the possibility to use rand with listNum.
Works well on 4.5.
I would not recommend you to do a random selection via TypoScript. Since you want a random image everytime you load the page, your random element must be a USER_INT or COA_INT element and is therefore not cacheable.
A simple solution for this is to do it via JavaScript. You define a default image if JavaScript is not available on the client, and a JavaScript that randomly select an image. With this solution, you get a random image everytime, and your content is fully cacheable.
The following TypoScript code can give you some inspiration for this. It basically reads out Image-Elements from the border collumn and generates JavaScript to output them randomly. Also, the images are linkable.
lib.teaser = COA
lib.teaser.10 = CONTENT
lib.teaser.10 < styles.content.getBorder
lib.teaser.10 {
slide = -1
table=tt_content
select{
begin = 0
max = 1
#language
languageField=sys_language_uid
#from wich column
where=colPos=3
}
wrap=<div class="teaserimage">|</div>
renderObj=COA
#image with gallery function
renderObj.10 = COA
renderObj.10 {
stdWrap.required=1
# get image
10 = IMAGE
10 {
#if not empty
required=1
file.import=uploads/pics/
file.import.field=image
#file.width=266
#file.height=180
file.import.listNum = 0
stdWrap.insertData=1
params = id="imgbig_{TSFE:currentRecord}"
imageLinkWrap < tt_content.image.20.1.imageLinkWrap
imageLinkWrap.typolink.ATagParams = id="link_imgbig_{TSFE:currentRecord}"
imageLinkWrap.typolink.ATagParams.stdWrap.insertData=1
}
# standard image configuration from tt_content
10.altText < tt_content.image.20.1.altText
10.titleText < tt_content.image.20.1.titleText
10.longdescURL < tt_content.image.20.1.longdescURL
# random function for gallery images
30 = COA
30 {
stdWrap.required=1
stdWrap.dataWrap(
<script type="text/javascript">
/* <![CDATA[ */
var imgArray = new Array(|);
var randnum = Math.round(Math.random()*(imgArray.length-1));
document.getElementById('imgbig_{TSFE:currentRecord}').src ='uploads/pics/' + imgArray[randnum];
/* ]]> */
</script>
)
# first gallery image
10 = TEXT
10.field = image
10.listNum.splitChar=,
10.listNum=0
10.if.isTrue.field=image
10.if.isTrue.listNum=1
10.if.isTrue.listNum.splitChar=,
10.dataWrap = "|"
# other gallery images
20 = TEXT
20.field = image
20.split {
token = ,
cObjNum = 1
1 = COA
1.if.isPositive.data = TSFE:register|SPLIT_COUNT
1 {
10 = TEXT
10.data = current:1
10.dataWrap = ,"|"
}
}
}
# random function for gallery links
40 = COA
40 {
stdWrap.required=1
stdWrap.dataWrap(
<script type="text/javascript">
/* <![CDATA[ */
//var imgLinkArray = new Array(randnum);
var imgLinkArray = new Array(|);
if(document.getElementById('link_imgbig_{TSFE:currentRecord}')) document.getElementById('link_imgbig_{TSFE:currentRecord}').href = imgLinkArray[randnum];
/* ]]> */
</script>
)
# first gallery link
10 = TEXT
10.field = image_link
10.listNum.splitChar=,
10.listNum=0
10.dataWrap = "|"
10.typolink.parameter.field = image_link
10.typolink.returnLast = url
# other gallery links
20 = TEXT
20.field = image_link
20.split {
token = ,
cObjNum = 1
1 = COA
1.if.isPositive.data = TSFE:register|SPLIT_COUNT
1 {
10 = TEXT
10.data = current:1
10.dataWrap = ,"|"
10.typolink.parameter.data = current:1
10.typolink.returnLast = url
}
}
}
}
}
only typo3 V4.6 and above has listNum = rand in the core, see this http://forge.typo3.org/issues/16180
for typo3 V4.5 you have to extend tslib/the class.tslib_content.php
add this to localconf.php:
$TYPO3_CONF_VARS['FE']['XCLASS']['tslib/class.tslib_content.php'] = PATH_site.'fileadmin/template/class.ux_tslib_content.php';
and add this code to class.ux_tslib_content.php:
<?php
/***************************************************************
* Copyright notice
*
* (c) 1999-2008 Kasper Skaarhoj (kasperYYYY#typo3.com)
* All rights reserved
*
* This script is part of the TYPO3 project. The TYPO3 project is
* free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* The GNU General Public License can be found at
* http://www.gnu.org/copyleft/gpl.html.
* A copy is found in the textfile GPL.txt and important notices to the license
* from the author is found in LICENSE.txt distributed with these scripts.
*
*
* This script is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* This copyright notice MUST APPEAR in all copies of the script!
***************************************************************/
/**
* Contains classes for Content Rendering based on TypoScript Template configuration
*
* $Id: class.tslib_content.php 4254 2008-09-27 13:35:44Z dmitry $
* Revised for TYPO3 3.6 June/2003 by Kasper Skaarhoj
* XHTML compliant
*
* class tslib_cObj : All main TypoScript features, rendering of content objects (cObjects). This class is the backbone of TypoScript Template rendering.
*
* #author Kasper Skaarhoj <kasperYYYY#typo3.com>
*/
/**
* This class contains all main TypoScript features.
* This includes the rendering of TypoScript content objects (cObjects).
* Is the backbone of TypoScript Template rendering.
*
* There are lots of functions you can use from your include-scripts.
* The class "tslib_cObj" is normally instantiated and referred to as "cObj".
* When you call your own PHP-code typically through a USER or USER_INT cObject then it is this class that instantiates the object and calls the main method. Before it does so it will set (if you are using classes) a reference to itself in the internal variable "cObj" of the object. Thus you can access all functions and data from this class by $this->cObj->... from within you classes written to be USER or USER_INT content objects.
*
* #author Kasper Skaarhoj <kasperYYYY#typo3.com>
* #package TYPO3
* #subpackage tslib
* #link http://typo3.org/doc.0.html?&tx_extrepmgm_pi1[extUid]=270&cHash=4ad9d7acb4
*/
class ux_tslib_cObj extends tslib_cObj{
/**
* Exploding a string by the $char value (if integer its an ASCII value) and returning index $listNum
*
* #param string String to explode
* #param string Index-number. You can place the word "last" in it and it will be substituted with the pointer to the last value. You can use math operators like "+-/*" (passed to calc())
* #param string Either a string used to explode the content string or an integer value which will then be changed into a character, eg. "10" for a linebreak char.
* #return string
*/
function listNum($content,$listNum,$char) {
$char = $char ? $char : ',';
if (t3lib_div::testInt($char)) {
$char = chr($char);
}
$temp = explode($char,$content);
$last = ''.(count($temp)-1);
if($listNum === 'rand'){ $listNum = rand(0,count($temp)-1);} //taywa added: rand feature!
$index=$this->calc(str_ireplace('last',$last,$listNum));
return $temp[$index];
}
}
?>
I know update-profile-picture translates to the updateProfilePictureAction() function.
Where does the conversion of the action param take place?
I'd like to get updateProfilePicture as a value, I could write the function but it must already be in the library somewhere.
When using $this->_getParam('action') it returns update-profile-picture;
/**
* Auto call scripts
* #see Zend_Controller_Action::postDispatch()
*/
public function postDispatch(){
$action = $this->_getParam('action',false);
$method = $action.'Scripts';
if ($action && method_exists($this, $method))
$this->$method();
}
this works fine for indexAction - indexScripts but not for updateProfilePictureScripts (looking for update-profile-pictureScripts)
Get it with
$this->getFrontController()->getDispatcher()->formatActionName($this->_getParam('action',null));
It is happening in
/Zend/Controller/Dispatcher/Abstract.php
/**
* Formats a string into an action name. This is used to take a raw
* action name, such as one that would be stored inside a Zend_Controller_Request_Abstract
* object, and reformat into a proper method name that would be found
* inside a class extending Zend_Controller_Action.
*
* #param string $unformatted
* #return string
*/
public function formatActionName($unformatted)
{
$formatted = $this->_formatName($unformatted, true);
return strtolower(substr($formatted, 0, 1)) . substr($formatted, 1) . 'Action';
}
/**
* Formats a string from a URI into a PHP-friendly name.
*
* By default, replaces words separated by the word separator character(s)
* with camelCaps. If $isAction is false, it also preserves replaces words
* separated by the path separation character with an underscore, making
* the following word Title cased. All non-alphanumeric characters are
* removed.
*
* #param string $unformatted
* #param boolean $isAction Defaults to false
* #return string
*/
protected function _formatName($unformatted, $isAction = false)
{
// preserve directories
if (!$isAction) {
$segments = explode($this->getPathDelimiter(), $unformatted);
} else {
$segments = (array) $unformatted;
}
foreach ($segments as $key => $segment) {
$segment = str_replace($this->getWordDelimiter(), ' ', strtolower($segment));
$segment = preg_replace('/[^a-z0-9 ]/', '', $segment);
$segments[$key] = str_replace(' ', '', ucwords($segment));
}
return implode('_', $segments);
}
Have a look at Zend_Filter_Inflector : http://framework.zend.com/manual/en/zend.filter.inflector.html