Creating PDF

I’m working on a site that generates a certificate (a pdf file) when somebody places an order. There are two types of products that can be ordered, one is a one off product the other has a recurring fee.

If somebody orders a products with a recurring fee then the pdf file is supposed to be generated. But if the product is a one off product then the pdf isn’t generated. If the order contains both then the one off product should be there.

I’ve managed to get it so that if the order is for a recurring product then a pdf is generated, but if the order is for (or contains) a one off product then I get a blank page.

What I really want is for all one off products to be ignored when generating the pdf.

I’ve got this:


public function getRecurringOrderProducts()
  {
    $products = array();
    foreach($this->getOrderProducts() as $op) {
      if($op->getProduct()->isRecurring()) {
        $products[] = $op;
      }
else {
exit;
}
    }

    return $products;
  }

I know the ‘exit’ line means that if the order contains a one off product then all subsequent php is ignore. I want to change that to effectively say if there are one off products ignore them but I don’t know how to do that.

Also I want to set ‘liveable’ to false in this part if the order contains ONLY one off products:


function isLiveable()
   {
   $liveable = true;

    if ($this->getPdfDate()) return false;
     if($op->getProduct()!->isRecurring()) return false;

   foreach($this->getOrderProducts() as $op) {
    if($op->getProduct()->needCli() && (strtolower($op->getCLI())=='tbc')) {
      $liveable = false;
    }
   }

   return $liveable;
  }

I’d be extremely grateful for any help on this as I’m completely vexed by it. I’ve spent weeks trying to sort it and am now stuck.

Thanks

For the first question, you just don’t need the else() clause, or you can leave it in place and just do nothing in there:


public function getRecurringOrderProducts()
  {
    $products = array();
    foreach($this->getOrderProducts() as $op) {
      if($op->getProduct()->isRecurring()) {
        $products[] = $op;
      }
    }
    return $products;
  }

That should return an array called products that only contains items where isRecurring() comes back as true.

For the second part, I’d expect you’d need to set liveable to false, then check every product in much the same way as you do in the first code block, and as soon as you find a true response to IsRecurring(), set liveable to true. You could then exit the foreach loop using break as there’s no point checking the rest once you’ve found the first that does not conform. You’d need to decide whether this needs to go before or during the rest of the checks you’re doing in there - if it will always be false if one is recurring, regardless of the other options, do it first then exit the function before it gets a chance to set it back to false.

Thank you, I did try that, but it added both the one off and recurring products to the pdf, I want it to not add the one off products. Is that possible?

You said that “If somebody orders a products with a recurring fee then the pdf file is supposed to be generated. But if the product is a one off product then the pdf isn’t generated. If the order contains both then the one off product should be there.” but I can read that a couple of different ways and I’m not sure I’ve read it correctly.

Do you mean that it should generate a PDF if there are products with a recurring fee, and it should not generate one for a one-off product, or that it should generate one for both, but it is not doing so for some reason? Does the last part mean that if the order has both, the PDF should be generated but also include the one-off item?

So should you generate a PDF:

  1. If the order only contains one-off item(s)
  2. If the order only contains an item or items with a recurring fee
  3. A mixture of both

And if a mixture of both will trigger a PDF being generated, does the PDF need to show all items, or just those with a recurring fee?

Does the PDF generating code look for something in the products array to decide whether to create a PDF? If it does, have you had a look at the contents of that array? If I’ve understood what’s happening, I can’t see from above how one-off items could get into the products array unless isRecurring() is returning the wrong value.

Sorry that’s probably my fault I wasn’t very clear. I want a pdf to be generated ONLY for recurring products. If the order doesn’t contain a recurring product then it shouldn’t generate one and if it contains both then it should only have the recurring product on it.

This is the code that I have at the moment:

The isRecurring function is:

public function isRecurring()
  {
    return $this->getPriceType() ? true : false;
  }

And PriceType is a database table that is either 1 (recurring) or 0 (non recurring)

The code used the generate the PDF is:


public function getPdfCertificateFilename($app)
    {
      return $app['config']['pdf'].'PdfCertificate'.$this->getId().'.pdf';
    }

    public function generatePdfCertificate($app)
    {
      $filename = $this->getPdfCertificateFilename($app);
      if (!file_exists($filename)) {
        // Generate HTML
        $html =  $app['twig']->render('order/pdfcertificate.twig', array(
          'entry'           => $this,
          'products'        => $products=$this->getRecurringOrderProducts(),
          'content_height'  => intval( (8*100*count($products))/95 )+1,
          'domain'          => $app['config']['domain'],
        ));
        // Generate PDF
        $snappy = new Pdf($app['wkhtmltox_bin']);
        $snappy->setOption('orientation', 'landscape');
        $snappy->generateFromHtml(utf8_decode($html), $filename);
      }

and


class PdfController
{
  public function pdfAction(Request $request, Application $app, $id, $date)
  {
    $go_live = strtotime(implode('-',array_reverse(explode('-',$date))));
    $order   = \\OrderQuery::create()->findPK($id);

    if ($order->getPdfDate()) {
      $filename = $order->getPdfCertificateFilename($app);
      header('Content-Type: application/pdf');
      header('Content-Length: '. filesize($filename));
      header('Content-Disposition: inline; filename="pdf-certificate-'.basename($filename));
      readfile($filename);
      exit;
    }

    if ($order && $pdf!==false)
    {
      if ($order->isLiveAble()) {
        $order->setPdfDate($pdf);
        $order->save();
        $order->generatePdfCertificate($app);
        $order->uploadPdfCertificateToZendesk($app);
      }
    }

    return $app->redirect('/order');
  }
}

Thank you for helping with this.

Ah, looking at that (though I’m not really up to speed with classes) it seems then that isLiveAble() is causing the trouble - that seems to be the only thing that you check to see if it’s correct to generate and upload the PDF certificate. I don’t know what needCLI does or whether it’s relevant.

Is the products array coming back correctly populated, or is that containing items that should not be there? If it has one-off items, then I’d guess isRecurring() must be the culprit because that’s the only thing you check when building the array. I don’t really know defaults for comparisons or whether it would make any difference, maybe you could change to


public function isRecurring()
  {
    return $this->getPriceType()==1 ? true : false;
  }

There doesn’t seem to be any check around creating the PDF file though - once you decide the order can be saved, you go on to create the PDF file and upload it, regardless of whether it has any suitable products in it. That wouldn’t explain it including both types of product, but it would explain getting a blank PDF if you only have one-off items.

All of the fields are properly populated. The CLI is a reference number that is needed for some products, mainly recurring products but only all of them.

I agree that it must be the isRecurring() part that is causing the problem but I’ve spent two weeks trying to get this to work. All I want to do at the moment is add only recurring products to the PDF. I’m getting desperate to get this sorted and am so grateful to you helping me with this.

I must be getting confused now.

If all the fields are properly populated, does that mean that the products array is correct and only includes items that have a recurring fee? If so, isRecurring() must be working, and I can’t see how it can create a PDF containing items that aren’t in the products array. I can see how it creates a blank PDF, but not one containing one-off products.

If the product array has the one-off items in it too, then isRecurring() must be the culprit, and the only thing I can suggest is add loads of debugging echo() statements and follow the code through. It’s laborious but the only way I know.

Sorry, I don’t think I’m being very clear. I’ve removed


else {
exit;
}

from the code so it’s now like this


public function getRecurringOrderProducts() 
  { 
    $products = array(); 
    foreach($this->getOrderProducts() as $op) { 
      if($op->getProduct()->isRecurring()) { 
        $products[] = $op; 
      } 
    } 
    return $products; 
  } [/code and it now generates a PDF regardless of what the order contains. The problem is that I don't want a pdf if the product is a one off. Is there anything that I can do so change the above code so that is says if the product is recurring do this, if its not then ignore it?

But, what does the PDF have on it?

That code you have creates a products array based on the return from the isRecurring() function for each prioduct in your order array. So at the end, products should either be an empty array if you only have one-off products, an array containing all order items if you only have items with recurring fees, or an array containing just items with recurring fees if you have both types on the order.

That’s the first thing to check. Change your getRecurringOrderProducts() function to say this:


public function getRecurringOrderProducts()
  {
  $products = array();
  foreach($this->getOrderProducts() as $op) {
    if($op->getProduct()->isRecurring()) {
      $products[] = $op;
      }
    }
var_dump($products);
exit;
}

Then run it with three different order types - one that has only one-off items, one that has a mixture, and one that has only items with recurring fees. When your code calls that function, it should build the array containing only products where isRecurring() returns a true value, then var_dump() will display it on the screen so you can check the contents. If it does not contain the correct items, then the issue is in isRecurring() bringing out too many items. As I said above, it will create an array that might be empty, or might just have items with recurring fees. If it includes any that do not have recurring fees, then isRecurring() has a fault and you need to debug that.

Once it creates the products array with the correct items, and only the correct items, then move on to why it creates a PDF regardless.

Sorry the pdf has all of the products, the CLI, description and expiry date, obviously the expiry date isn’t needed for one off products so this is what Ive trying to do this.

I’ve used the code you suggested and that array works fine - it doesn’t show any one off products regardless. I’m guessing I then need to try that var_dump() else where in the code?

I’ve got as far as this


public function generatePdfCertificate($app)
    {
      $filename = $this->getPdfCertificateFilename($app);
      if (!file_exists($filename)) {
        // Generate HTML
        $html =  $app['twig']->render('order/pdfcertificate.twig', array(
          'entry'           => $this,
          'products'        => $products=$this->getRecurringOrderProducts(),
          'content_height'  => intval( (8*100*count($products))/95 )+1,
          'domain'          => $app['config']['domain'],
        ));
        // Generate PDF
        $snappy = new Pdf($app['wkhtmltox_bin']);
        $snappy->setOption('orientation', 'landscape');
        $snappy->generateFromHtml(utf8_decode($html), $filename);
      }
	  var_dump($products);  
exit;
    }

again the var_dump($products) is showing only the recurring products are part of that but for some reason it’s still showing the one off products in the pdf.

Okay I think we’re getting somewhere, I changed this part:

function isLiveable()
    {
      $liveable = true;

      if ($this->getGoLiveDate()) return false;

      foreach($this->getOrderProducts() as $op) {
        if($op->getProduct()->needCli() && (strtolower($op->getCLI())=='tbc')) {
          $liveable = false;
        }
        if($op->getProduct()->isRecurring()) {
          $liveable = false;
        }
		else {
			$liveable - true;
		}
      }

      return $liveable;
    }

Now it ono gives me an option to generate the pdf if the order is for recurring products - problem is that if it contains both then there’s no option to generate that, there should be just it should only contain the recurring ones

OK, so if the products array does not include anything other than the products you want, the issue must be in the PDF generation code, so you’ll need to step through that adding var_dumps until you find the problem - you know that what you’re passing in is correct at least.

So if you want to return false if all products are one-offs, you need to set it to false before the start of the loop, then set it to true when you find the first one that has a recurring fee, then exit the loop because there’s no point looking through the rest. You might need to use a separate loop and a separate flag, then assess at the end of both loops. Your code sets the flag back on the opposite condition, so it will just be set to the flag for the last item you checked.


$alloneoffs = true;
foreach ($this->GetOrderProducts() as $op) {
  if ($op->GetProduct()->isRecurring()) {
    $alloneoffs = false;
    break;
    }
  }

I tried changing this

public function getRecurringOrderProducts()
  {
    $products = array();
	$alloneoffs*=*true;
    foreach($this->getOrderProducts() as $op) {
      if($op->getProduct()->isRecurring()) {
        $products[] = $op;
        $alloneoffs*=*false;
        break;
      }
    }

    return $products;
  }

but that didn’t seem to make a difference, it still is adding everything to the pdf. I understand what you’re saying and it makes sense but I’m struggling to work out how to actually do it.

Well, that won’t make any difference there, because you’re not returning $alloneoffs from your function, so while it might get set correctly, it won’t alter anything. I meant to add it into isLiveable(), but because I’m not really sure what the rest of that function is doing I didn’t want to say where.

But this isn’t anything to do with what products it’s adding into the PDF though. You’ve already established that $products contains only the correct items when you build it in getRecurringOrderProducts(), so you need to see what the ‘converttoHTML’ function does with it, or whatever specific part actually creates the PDF.

Okay I think I might know where the problem is then, the pdf is generated from a twig file using this code:

{% block list_tbody %}
  <tr valign="top">
    <td><ul>
        {% for op in entry.orderProducts %}
        <li>{{ op.cli }}</li>
        {%  endfor %}
      </ul></td>
    <td>
      <ul>
        {% for op in entry.orderProducts %}
        <li>{{ op.product }} &pound;{{ op.unitPrice }} x {{ op.quantity }}</li>
        {%  endfor %}
      </ul>
    </td>
    <td>{{ entry.billDescription|nl2br }}</td>
    <td><!--{{ entry.getPdfDate|date("d/m/Y") }} - -->{{ entry.getExpiryDate|date("d/m/Y") }}</td>
  </tr>
{% endblock %}

I tried changing entry.orderProducts to entry.orderRecurringProducts and op.product to op.recurringProduct but that gave me an error saying

BadMethodCallException: Call to undefined method: orderRecurringProducts.

and

Twig_Error_Runtime: An exception has been thrown during the rendering of a template (“Call to undefined method: orderRecurringProducts.”) in “order/pdfcertificate.twig” at line 44.
line 44 is

{% for op in entry.orderProducts %}

I did start having a bit of a look at twig, but didn’t really get very far. That template doesn’t seem to make sense though. When you call the renderer, you create an array called ‘products’ which contains only the recurring items, but then in that template above, you’re running through entry.orderProducts, with ‘entry’ being passed in as $this. So it doesn’t matter what you put into the $products array, because you’re not using it in the template.

I’m guessing that in your class you have an array called orderProducts which is referenced in your template, if that’s the case your modification is only going to work if you also have an array called orderRecurringProducts, which I guess you don’t because you wouldn’t need to build $products in the first place.

So you either need to modify your template to access the products array that you pass in to it and get the values from there, or you need to modify your class to have an orderRecurringProducts array that only contains the relevant products.

Once you’ve done that, you’ll still have the issue of it generating blank PDFs if there are no recurring items on, because you’re not checking it first. My quick and dirty approach would be to change the layout of the call to something like:


public function generatePdfCertificate($app)
    {
      $products = =$this->getRecurringOrderProducts();
      if count($products) > 0 {
        $filename = $this->getPdfCertificateFilename($app);
        if (!file_exists($filename)) {
          // Generate HTML
          $html =  $app['twig']->render('order/pdfcertificate.twig', array(
            'entry'           => $this,
            'products'        => $products,
            'content_height'  => intval( (8*100*count($products))/95 )+1,
            'domain'          => $app['config']['domain'],
          ));
          // Generate PDF
          $snappy = new Pdf($app['wkhtmltox_bin']);
          $snappy->setOption('orientation', 'landscape');
          $snappy->generateFromHtml(utf8_decode($html), $filename);
        }
      }
    }

so it creates the array of recurring items, checks if there are any, and only generates the pdf if there are.

Am I doing something wrong? It’s still showing both recurring and one of products on the pdf. I changed the code to this:


public function generatePdfCertificate($app)
    {
	$products = $this->getRecurringOrderProducts();
	if (count($products) > 0) {
      $filename = $this->getPdfCertificateFilename($app);
      if (!file_exists($filename)) {
        // Generate HTML
        $html =  $app['twig']->render('order/pdfcertificate.twig', array(
          'entry'           => $this,
          'products'        => $products,
          'content_height'  => intval( (8*100*count($products))/95 )+1,
          'domain'          => $app['config']['domain'],
        ));
        // Generate PDF
        $snappy = new Pdf($app['wkhtmltox_bin']);
        $snappy->setOption('orientation', 'landscape');
        $snappy->generateFromHtml(utf8_decode($html), $filename);
      }
    }
}

I even tried removing the whole ‘generatePdfCertificate’ function to see if something else was generating it but it’s definitely that function

I’ve also just checked the generateFromHtml function so see if that was causing any problems and that is:

public function generateFromHtml($html, $output, array $options = array(), $overwrite = false)
    {
        $filename = $this->createTemporaryFile($html, 'html');

        $this->generate($filename, $output, $options, $overwrite);

        $this->unlink($filename);
    }