I'm trying to render a pdf using CakePdf 3.5.3 using the wkhtmltopdf engine with html headers on every page showing custom text as well as the page number.
https://wkhtmltopdf.org/usage/wkhtmltopdf.txt describes the process as passing the --header-html command line argument and using the following javascript and html:
<!DOCTYPE html>
<html><head><script>
function subst() {
var vars = {};
var query_strings_from_url = document.location.search.substring(1).split('&');
for (var query_string in query_strings_from_url) {
if (query_strings_from_url.hasOwnProperty(query_string)) {
var temp_var = query_strings_from_url[query_string].split('=', 2);
vars[temp_var[0]] = decodeURI(temp_var[1]);
}
}
var css_selector_classes = ['page', 'frompage', 'topage', 'webpage', 'section', 'subsection', 'date', 'isodate', 'time', 'title', 'doctitle', 'sitepage', 'sitepages'];
for (var css_class in css_selector_classes) {
if (css_selector_classes.hasOwnProperty(css_class)) {
var element = document.getElementsByClassName(css_selector_classes[css_class]);
for (var j = 0; j < element.length; ++j) {
element[j].textContent = vars[css_selector_classes[css_class]];
}
}
}
}
</script></head><body style="border:0; margin: 0;" onload="subst()">
<table style="border-bottom: 1px solid black; width: 100%">
<tr>
<td class="section"></td>
<td style="text-align:right">
Page <span class="page"></span> of <span class="topage"></span>
</td>
</tr>
</table>
</body></html>
I created src/Template/Layout/pdf/default.ctp and src/Template/Layout/pdf/header.ctp
default.ctp
<!DOCTYPE html>
<html lang="en">
<head>
<?= $this->Html->charset() ?>
<title>
<?= $this->fetch('title') ?>
</title>
<?= $this->Html->meta('icon') ?>
</head>
<body>
<?= $this->Flash->render() ?>
<div>
<?= $this->fetch('content') ?>
</div>
</body>
</html>
header.ctp
<!DOCTYPE html>
<html lang="en">
<head>
<script>
function subst() {
var vars = {};
var query_strings_from_url = document.location.search.substring(1).split('&');
for (var query_string in query_strings_from_url) {
if (query_strings_from_url.hasOwnProperty(query_string)) {
var temp_var = query_strings_from_url[query_string].split('=', 2);
vars[temp_var[0]] = decodeURI(temp_var[1]);
}
}
var css_selector_classes = ['page', 'frompage', 'topage', 'webpage', 'section', 'subsection', 'date', 'isodate', 'time', 'title', 'doctitle', 'sitepage', 'sitepages'];
for (var css_class in css_selector_classes) {
if (css_selector_classes.hasOwnProperty(css_class)) {
var element = document.getElementsByClassName(css_selector_classes[css_class]);
for (var j = 0; j < element.length; ++j) {
element[j].textContent = vars[css_selector_classes[css_class]];
}
}
}
}
</script>
<title></title>
</head>
<body onload="subst()">
<?= $some_view_variable ?>
Page <span class="page"></span> of <span class="topage"></span>
</body>
</html>
This is how I configure CakePdf in my controller.
Configure::write('CakePdf', [
'engine' => [
'className' => 'CakePdf.WkHtmlToPdf',
'binary' => "/usr/local/bin/wkhtmltopdf",
'options' => [
'header-html' => APP . 'Template' . DS . 'Layout' . DS . 'pdf' . DS . 'header.ctp',
],
],
'margin' => [
'bottom' => 0,
'left' => 0,
'right' => 0,
'top' => 0
],
'pageSize' => 'Letter',
'orientation' => 'portrait'
]);
The header information is not shown in the generated PDF. I'm also not sure how to pass viewVars to the header template.
My understanding is that the header must be in a .html file, it must be raw HTML (e.g. not a Cake template), and I've not found a way to specify the header-html option. Here's the hack I've used to work around it:
$view = new View();
$dir = TMP . 'html';
if (!is_dir($dir)) {
mkdir($dir);
}
while (true) {
// WkHtmlToPdf requires .html extension on these files, but tempnam can't do that.
$file = tempnam($dir, 'header');
if (rename($file, $file . '.html')) {
$file .= '.html';
break;
} else {
unlink($file);
}
}
file_put_contents($file, '<!DOCTYPE HTML><html lang=\'en-US\'><body style=\'font-family: "Times New Roman"\'>' . $view->element($element) . '</body></html>');
// Hack to access protected member: we want --header-html on the command line.
// This is easier than extending the class for a single such usage.
$closure = function($file) {
$this->_header = ['html' => 'file://' . $file];
};
$hack = \Closure::bind($closure, $pdf, 'CakePdf\Pdf\CakePdf');
$hack($file);
$this->_tmp[] = $file;
This is, of course, all off in a separate function, so it's easy to replace if I find a better way. :-)
Related
I'm trying to get an ajax request working in CakePHP4, but keep running into the CSRF protection.
In my controller:
public function beforeFilter(EventInterface $event)
{
parent::beforeFilter($event);
$this->Security->setConfig('unlockedActions', ['players']);
}
public function players()
{
$players = ['Sjaak Afhaak', 'Aad Kippezaad', 'Gras Kaffer', 'Tedje van Es'];
if ($this->request->is('ajax')) {
$this->response = $this->response->withDisabledCache();
}
$letter = trim($this->request->getData('letter'));
if (!empty($letter)) {
$players = array_filter($spelers, function ($haystack) use ($letter) {
return(strpos($haystack, $letter));
});
}
$this->set(compact('players'));
$this->viewBuilder()->setOption('serialize', ['players']);
$this->RequestHandler->renderAs($this, 'json');
}
Then in the template file:
<?php echo $this->Html->script('jquery.min'); ?>
<div class="container">
<div class="row">
<div class="column">
<div class="users form content">
<?php echo $this->Form->create(null, ['url' => \Cake\Routing\Router::pathUrl('Ado::players')]); ?>
<fieldset>
<legend>Players</legend>
<?php echo $this->Form->text('letter', ['placeholder' => 'Begin letter(s)']); ?>
</fieldset>
<?= $this->Form->button('OK'); ?>
<?= $this->Form->end() ?>
</div>
</div>
<div class="column response">
...
</div>
</div>
</div>
<script>
$(document).ready(function () {
$(document).on("submit", "form", function (event) {
var $form = $(this);
var $target = $('div.response');
var csrf = $('input[name=_csrfToken]', $form).val();
var data = { letter: $('input[name=letter]', $form).val() };
$target.html('');
$.ajax({
method: "POST",
url: $form.attr('action'),
beforeSend: function(xhr) {
xhr.setRequestHeader('Content-type', 'application/x-www-form-urlencoded');
xhr.setRequestHeader('X-CSRF-Token', csrf);
},
data: data,
dataType: "json"
})
.done(function (response) {
var items = [];
$.each( response, function( key, val ) {
items.push( "<li id='" + key + "'>" + val + "</li>" );
});
$( "<ul/>", {
"class": "spelers-list",
html: items.join( "" )
}).appendTo( $target );
});
event.preventDefault();
});
});
</script>
Without the beforeSend in the ajax call, i get a 403 response.
If i include the X-CSRF-Token i receive a 400 response.
2020-06-18 09:49:38 Error: [Cake\Http\Exception\BadRequestException] `_Token` was not found in request data. in src\vendor\cakephp\cakephp\src\Controller\Component\FormProtectionComponent.php on line 143
Stack Trace:
- src\vendor\cakephp\cakephp\src\Controller\Component\FormProtectionComponent.php:97
- src\vendor\cakephp\cakephp\src\Event\EventManager.php:309
- src\vendor\cakephp\cakephp\src\Event\EventManager.php:286
- src\vendor\cakephp\cakephp\src\Event\EventDispatcherTrait.php:92
- src\vendor\cakephp\cakephp\src\Controller\Controller.php:569
- src\vendor\cakephp\cakephp\src\Controller\ControllerFactory.php:72
- src\vendor\cakephp\cakephp\src\Http\BaseApplication.php:229
- src\vendor\cakephp\cakephp\src\Http\Runner.php:77
- src\vendor\cakephp\cakephp\src\Http\Middleware\BodyParserMiddleware.php:164
- src\vendor\cakephp\cakephp\src\Http\Runner.php:73
- src\vendor\cakephp\authorization\src\Middleware\AuthorizationMiddleware.php:129
- src\vendor\cakephp\cakephp\src\Http\Runner.php:73
- src\vendor\cakephp\authentication\src\Middleware\AuthenticationMiddleware.php:124
- src\vendor\cakephp\cakephp\src\Http\Runner.php:73
- src\vendor\cakephp\cakephp\src\Http\Runner.php:77
- src\vendor\cakephp\cakephp\src\Http\Middleware\CsrfProtectionMiddleware.php:138
- src\vendor\cakephp\cakephp\src\Http\Runner.php:73
- src\vendor\cakephp\cakephp\src\Http\Runner.php:58
- src\vendor\cakephp\cakephp\src\Routing\Middleware\RoutingMiddleware.php:166
- src\vendor\cakephp\cakephp\src\Http\Runner.php:73
- src\vendor\cakephp\cakephp\src\Routing\Middleware\AssetMiddleware.php:68
- src\vendor\cakephp\cakephp\src\Http\Runner.php:73
- src\vendor\cakephp\cakephp\src\Error\Middleware\ErrorHandlerMiddleware.php:119
- src\vendor\cakephp\cakephp\src\Http\Runner.php:73
- src\vendor\cakephp\debug_kit\src\Middleware\DebugKitMiddleware.php:60
- src\vendor\cakephp\cakephp\src\Http\Runner.php:73
- src\vendor\cakephp\cakephp\src\Http\Runner.php:58
- src\vendor\cakephp\cakephp\src\Http\Server.php:90
- src\webroot\index.php:41
Not sure if it's related, but i'm using the Authentication plugin. (https://book.cakephp.org/authentication/2/en/index.html)
As can be seen in the stacktrace, the error stems from the form protection component, not the security component, so unlocking actions on the security component won't do anything.
The security component is deprecated (the Cookbook doesn't seem to mention that), and the form protection component is one of the utilities that are ment to replace it (others are the CSRF middleware and the HTTPS enforcer middleware) - you shouldn't use both, drop the security component (and by that I mean to also remove the related loadComponent() call), and configure the form protection component accordingly instead!
$this->FormProtection->setConfig('unlockedActions', ['players']);
The docs really need some overhaul here, not only is there no deprecation notice for the security component, but also the form protection component isn't listed in the components section.
I am very new to chrome extensions and react. An overview of what I am doing is pretty straight forward. I have a chrome extension which takes a screenshot of the activeTab and I want to send that screenshot from the extension to my react app.
From what I understand, I need to dispatch an event from the extension and have the react app listen to it. I have an event listener in the index.html of my react app to listen to events that are dispatched by the extension. I have been unsuccessful in my attempts.
Here's what I have so far:
Chrome extension
popup.js
let tabImage = document.getElementById('tabImage');
let capturedImage = null;
tabImage.onclick = () => {
chrome.tabs.captureVisibleTab(null, (image) => {
document.dispatchEvent(new CustomEvent('csEvent', { data: image })) // send image to react app
viewScreenshot(image);
});
}
//Create a new window in the browser with the captured image
viewScreenshot = (capturedImage) => {
const b64 = capturedImage;
const contentType = 'image/jpeg';
const byteCharacters = atob(b64.substr(`data:${contentType};base64,`.length));
const byteArrays = [];
for (let offset = 0; offset < byteCharacters.length; offset += 1024) {
const slice = byteCharacters.slice(offset, offset + 1024);
const byteNumbers = new Array(slice.length);
for (let i = 0; i < slice.length; i++) {
byteNumbers[i] = slice.charCodeAt(i);
}
const byteArray = new Uint8Array(byteNumbers);
byteArrays.push(byteArray);
}
const blob = new Blob(byteArrays, { type: contentType });
const blobUrl = URL.createObjectURL(blob);
window.open(blobUrl, '_blank');
}
popup.html
<html lang="en">
<head>
</head>
<body>
<button id="tabImage">Get a screenshot!</button>
<script src='popup.js'></script>
</body>
</html>
reactJS app
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>Shots</title>
</head>
<body>
<iframe id="qa_films" src="./demo_course_classic_player_html5-flash-AMP/story.html"
style="position:absolute ;top:0; left:0; bottom:0; right:0; width:100%; height:90%; border:none; margin:0 auto; padding:0; z-index: 0; overflow:hidden; "></iframe>
<div id="screenshot"></div>
</body>
<script>
document.body.addEventListener('csEvent', (event) => {
console.log(event);
})
</script>
</html>
I would like to know which part I am doing it wrong or is there a better way of implementing what I'm trying to achieve. Any help is appreciated. Thanks!
I'm using google charts with this code:
<?php
function testing($chartId, $chartFunc, $chartTitle, $xAxisTitle, $chartData, $chartType)
{
$pageMeat =<<<EOD
<html>
<head>
<script type="text/javascript" src="https://www.google.com/jsapi"></script>
<script type="text/javascript">
google.load("visualization", "1", {packages:["corechart"]});
google.setOnLoadCallback($chartFunc);
function $chartFunc() {
var data = google.visualization.arrayToDataTable($chartData);
var options = {
title: '$chartTitle',
hAxis: {title: '$xAxisTitle', titleTextStyle: {color: 'red'}}
};
EOD;
if($chartType == "line") {
$pageMeat .=<<<EOD
var chart = new google.visualization.LineChart(document.getElementById('$chartId'));
EOD;
}
else if($chartType == "pie") {
$pageMeat .=<<<EOD
var chart = new google.visualization.PieChart(document.getElementById('$chartId'));
EOD;
}
else {
$pageMeat .=<<<EOD
var chart = new google.visualization.ColumnChart(document.getElementById('$chartId'));
EOD;
}
$pageMeat .=<<<EOD
chart.draw(data, options);
}
</script>
</head>
<body>
<div id="$chartId" style="width: 900px; height: 500px;"></div>
</body>
</html>
EOD;
echo $pageMeat;
}
$gChartId = "vertColumns";
$gChartFn = "columnChart";
$gChartTitle = "Company Performance";
$gXAxisTitle = "Year";
$gChartData[] = array('Year', 'Sales', 'Expenses');
$gChartData[] = array('2004', 1000, 400);
$gChartData[] = array('2005', 1170, 460);
$gChartData[] = array('2006', 660, 1120);
$gChartData[] = array('2007', 1030, 540);
testing($gChartId, $gChartFn, $gChartTitle, $gXAxisTitle, json_encode($gChartData), "column");
?>
It works with line, pie and columncharts but when i try to use a table chart https://developers.google.com/chart/interactive/docs/gallery/table
It doesn't seem to work, how can i use a array with this table chart?
Thank you for help
you need to include the table package...
packages:["corechart", "table"] // <-- include table package here
I try to insert multiple arrays into database using codeigniter and ajax. After click the submit, database insert to mysql but all the value is blank. What did I miss?
My view:
<input type="hidden" name="iduser[]" id="iduser[]" value="<?php echo $rowmyuser->iduser; ?>"/>
<input type="hidden" name="idproduk[]" id="idproduk[]" value="<?php echo $rowproduk->idproduct; ?>"/>
<input type="text" class="form-control input-sm" name="settarget[]" id="settarget[]"/>
Save
My controller:
public function inserttarget()
{
$this->target_m->inserttarget_m();
}
My model:
function inserttarget_m() {
$iduser = $this->input->post("iduser");
$idproduk = $this->input->post("idproduk");
$settarget = $this->input->post("settarget");
$temp = count($this->input->post("iduser"));
$datatarget = array();
for($i=0; $i < 3; $i++)
{
$datatarget[]=array(
"iduser"=>$iduser[$i],
"idproduct"=>$idproduk[$i],
"target"=>$settarget[$i],
);
}
$this->db->insert_batch("tbl_targetsales",$datatarget);
}
The Ajax code:
function insert_target()
{
var DataString=$("#frm_target").serialize();
document.getElementById("save_target").innerHTML = "<i class='fa fa-cog fa-spin fa-lg fa-fw'></i> Saving...";
$.ajax({
type: 'POST',
url: '<?php echo base_url();?>target/inserttarget',
data: DataString,
success: function (data) {
//jQuery("#attendence_report_holder").html(response);
swal({
text: 'success'
});
$("#frm_target")[0].reset();
document.getElementById("save_target").innerHTML = "<i class='fa fa-save'> </i>Save";
window.location='<?php echo base_url(); ?>target';
},
error:function(data){
swal({
text: 'error',
});
document.getElementById("save_target").innerHTML = "<i class='fa fa-save'> </i>Save";
}
});
}
I need advice please, what is the problem in the code. Thanks in advance
first you call wrong the inputs in your html for the inputs name are
iduser[]
idproduk[]
settarget[]
and in your model you call with, and in the array you make to insert you call different variables that you save it
iduser
idproduct
target
replace all you madel with (using the correct names from form and variables names)
function inserttarget_m() {
$iduser = $this->input->post("iduser");
$idproduct = $this->input->post("idproduk");
$target = $this->input->post("settarget");
$temp = count($iduser);
$datatarget = array();
for($i=0; $i < $temp; $i++){
$datatarget[] = array(
"iduser" => $iduser[$i],
"idproduct" => $idproduct[$i],
"target" => $target[$i],
);
}
$this->db->insert_batch("tbl_targetsales", $datatarget);
}
In the following example, I should ask for the name of six students. They will be grouped according to bedroom type.
2 -> double
1 -> single
3 -> tiple
So, it means that I'll have a array of students (6 students). I would like to get their names. I was trying to create a variable like 'count' and put as ng-model of the input and increment during the loop, but it didn't work.
full html:
<!doctype html>
<html ng-app="sampleApp">
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
</head>
<body ng-controller='SampleController'>
<div ng-repeat='i in numberOfAccommodations track by $index'>
Bedroom {{$index}}
<span ng-repeat='x in numberOfStudents[$index]'>
Student {{$index}}
<input type='text' ng-model='abroadStudents[???].name' /> <!-- this input to student model -->
</span>
</div>
<input type='button' value='test' ">
<script>
angular.module('sampleApp',[]).controller('SampleController',function($scope){
$scope.abroadStudents = new Array[6];
$scope.abroadAccommodation = new Array();
$scope.abroadAccommodation.push({ "bedroomType": 2}, { "bedroomType": 1 }, {"bedroomType": 3});
$scope.numberOfAccommodations = function()
{
var arr = new Array();
for (var i = 0 ; i < $scope.abroadAccommodation.length ; i++)
{
arr.push(i);
}
return arr;
}();
$scope.numberOfStudents = function()
{
var arr = new Array();
for (var x = 0 ; x < $scope.abroadAccommodation.length ; x++)
{
var temp = 0;
var intArr = new Array();
do
{
intArr.push(temp);
temp++;
}
while(temp < $scope.abroadAccommodation[x].bedroomType);
arr.push(intArr);
}
return arr;
}();
});
</script>
</body>
</html>
I rewrote your logic to create a more logical structure of objects which does not require relying upon the $index. It creates an Array of room objects, then iterates through the array of abroadAccommodation. For each abroadAccommodation, it adds a room, and based on type, adds the appropriate number of student objects. It is then very easy to use ng-repeat to iterate through each room to identify each student.
Note I also am using angular.forEach here.
<!doctype html>
<html ng-app="sampleApp">
<head>
<script src="https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js"></script>
</head>
<body ng-controller='SampleController'>
<div ng-repeat="room in rooms">
{{room.roomNum}}
<div ng-repeat="student in room.students">
{{student.bed}}
<input ng-model="student.name" />
</div>
</div>
Student List:
<div ng-repeat="room in rooms">
<div ng-repeat="student in room.students">
{{student.name}}
</div>
</div>
<script>
angular.module('sampleApp', []).controller('SampleController', function($scope) {
$scope.abroadAccommodation = new Array();
$scope.abroadAccommodation.push({
"bedroomType ": 2
}, {
"bedroomType ": 1
}, {
"bedroomType ": 3
});
$scope.rooms = function() {
var arr = [];
angular.forEach($scope.abroadAccommodation, function(type, count) {
var room = {
"roomNum": "room " + (count + 1),
students: []
};
angular.forEach(type, function(numBeds) {
for (i = 0; i < numBeds; i++) {
room.students.push({
"bed": "bed " + (i + 1),
"name": "student" + Math.random()
});
}
arr.push(room);
})
});
return arr;
}();
});
</script>
</body>
</html>
http://plnkr.co/edit/YaPo54NUBPk9AnZkGcCc?p=preview