<?php
/**
 * phpaga
 *
 * pdf output functionality.
 *
 * This file contains the necessary classes and routines to create pdf documents.
 *
 * @author Florian Lanthaler <florian@phpaga.net>
 * @version $Id$
 *
 * Copyright (c) 2002, Florian Lanthaler <florian@phpaga.net>
 *
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *    * Redistributions of source code must retain the above copyright
 *      notice, this list of conditions and the following disclaimer.
 *
 *    * Redistributions in binary form must reproduce the above copyright
 *      notice, this list of conditions and the following disclaimer in
 *      the documentation and/or other materials provided with the
 *      distribution.
 *
 *    * Neither the name of Florian Lanthaler nor the names of his
 *      contributors may be used to endorse or promote products derived
 *      from this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

require_once(PHPAGA_TCPDF_DIR.'config/lang/eng.php');
require_once(PHPAGA_TCPDF_DIR.'tcpdf.php');

define('SEPARATOR_LINE', 1000);
define('MODE_TASKLIST', 1);
define('MODE_TIMESHEET', 2);

/**
 * This is the core PDF class. If needed, custom classes can extend this class 
 * to achieve a different layout. For invoices and quotations, the classes BillPDF and QuotationPDF 
 * (which both in turn extend PhPagaPDF) should be extended respectively.
 */
class PhPagaPDF extends TCPDF {


    /* Default font name */
    protected $defaultFontName = 'times';
    #protected $defaultFontName = PDF_FONT_NAME_MAIN;

    /* The following two members store the top and bottom position of the recipient - 
     * do not set them directly. */
    protected $_recipient_top = null;
    protected $_recipient_bottom = null;

    /**
     * Constructor method - calls the parent's __construct() method and sets a 
     * few parameters.
     *
     * @param $orientation
     * @param $unit
     * @param $format
     * @param $unicode
     * @param $encoding
     * @param $diskcache
     *
     * @return void
     */
    public function __construct($orientation='P', $unit='mm', $format='A4', $unicode=true, $encoding='UTF-8', $diskcache=false) {
        parent::__construct($orientation, $unit, $format, $unicode, $encoding, $diskcache);

        $this->SetCreator(PDF_CREATOR);
        $this->SetAuthor('phpaga');
        $this->SetAutoPageBreak(true, PDF_MARGIN_BOTTOM);
        $this->setImageScale(PDF_IMAGE_SCALE_RATIO);
    }

    /**
     * Draws a horizontal line.
     *
     * @param float $x1       x left (start)
     * @param float $x2       x right (end)
     * @param float $y        y coordinate
     * @param array $color    Color - for example, red = array(255,0,0)
     * @param float $width    Line width
     *
     * @return void
     */
    public function HorizontalLine($x1, $x2, $y, $color=null, $width=0.1) {
        if (is_null($color))
            $color = array(0,0,0);
        $this->Line($x1, $y, $x2, $y, array('color' => $color, 'width' => $width));
    }

    /**
     * This has been left intentionally blank.
     *
     * @return void
     */
    public function Header() {
    }


    /**
     * Outputs a footer.
     *
     * @return void
     */
    public function Footer() {
        $this->SetY(-15);
        $y = $this->GetY();
        $this->HorizontalLine(0, $this->getPageWidth(), $y, array(255,0,0));
        $this->SetFont($this->defaultFontName, '', 8);
        $this->Cell(0, 10, PHPAGA_LETTERHEAD_FOOTER, 0, false, 'C');
    }

    /**
     * Sets the default font name.
     *
     * @param string $fontname   Font name
     *
     * @return void
     */
    public function setDefaultFontName($fontname) {
        $this->defaultFontName = $fontname;
    }

    /**
     * Inserts a formatted table.
     *
     * @param array $columns    Array with column information
     * @param array $rows       Array with data to be displayed
     * @param array $options    Array with options (optional)
     *
     * @return void
     */
    public function AddTable($columns, $rows, $options=array()) {
        $default_options = array(
            'fontSize' => 8, 
            'headerFontSize' => 10, 
            'headerPrint' => true, 
            'headerLine' => true, 
            'footerLine' => true, 
            'x' => 20, 
            'title' => null,
            'fill' => false);

        $options = array_merge($default_options, $options);
        
        $x = $options['x'];

        if (strlen($options['title'])) {
            $this->SetX($x);
            $this->SetFont($this->defaultFontName, '', $options['headerFontSize']);
            $this->SetTextColor(100);
            $this->Cell(0, 0, $options['title'], 0, 1);
            $this->Ln(2);
        }

        $ln = 0;
        $last_item = end($columns);
        $coldata = array();
        $this->SetFont($this->defaultFontName, '', $options['fontSize']);
        $this->SetTextColor(100);
        foreach ($columns as $c) {
            if ($c == $last_item)
                $ln = 1;

            $align = isset($c['align']) ? $c['align'] : 'L';
            if (isset($c['type']) && ($c['type']=='amount'))
                $align = 'R';

            $height = isset($c['height']) ? $c['height'] : 0;

            if ($options['headerPrint'])
                $this->MultiCell($c['width'], $height, $c['title'], 0, $align, 0, $ln, $x);

            $coldata[] = array(
                'align' => $align, 
                'name' => $c['name'], 
                'height' => $height, 
                'width' => $c['width'], 
                'x' => $x,
                'type' => isset($c['type']) ? $c['type'] : null
            );

            $x += $c['width'];
        }
        $max_x = $x;
        $this->SetTextColor(0);

        if ($options['headerLine']) {
            $y = $this->GetY();
            $this->HorizontalLine(20, $max_x, $y, array(150,150,150));
            $this->SetY($y+1);
        }

        $this->SetFont($this->defaultFontName, '', $options['fontSize']);

        $this->SetTextColor(50);
        foreach ($rows as $r) {

            if (!is_array($r) && ($r == SEPARATOR_LINE)) {
                $_sly = $this->GetY()+1;
                $this->HorizontalLine($options['x'], $max_x, $_sly, array(100,100,100));
                $this->SetY($this->GetY()+1);
            } else {

                $rowdata = array();
                foreach ($coldata as $c) {
                    $_a = $c;
                    if (isset($r[$c['name']]) && strlen($r[$c['name']])) {
                        $_a['value'] = $r[$c['name']];

                        switch ($c['type']) {

                        case 'date':
                            $_a['value'] = date(PHPAGA_DATEFORMAT_PRINT, strtotime($_a['value']));
                            break;

                        case 'amount':
                            $_a['value'] = number_format($_a['value'], 2, PHPAGA_SEPARATOR_DECIMALS, PHPAGA_SEPARATOR_THOUSANDS);
                            break;

                        default:
                            break;
                        }
                    } else
                        $_a['value'] = '';
                    $rowdata[] = $_a;
                }

                $this->MultiRow($rowdata, $options['fill']);
            }
        }
        $this->SetTextColor(0);

        if ($options['footerLine']) {
            $y = $this->GetY()+1;
            $this->HorizontalLine(20, $max_x, $y, array(150,150,150));
            $this->SetY($y+1);
        }

        $this->SetY($this->GetY()+5);
    }

    /**
     * Calculates the height of a table row.
     *
     * @param int $numLines    Number of lines
     *
     * @return $rowHeight      Height of the row
     */
    private function GetRowHeight($numLines){
        if ($numLines == 0) 
            return 0;

        $m = $this->getCellMargins();
        if (isset($m['T']) && is_numeric($m['T']))
            $margin = $m['T'] * 2;
        else 
            $margin = 0;

        $lh = $this->GetFontSize() * $this->getCellHeightRatio();
        $rowHeight = ($numLines * $lh) + $margin;
        return $rowHeight;
    }

    /**
     * Outputs a row with multiple cells.
     *
     * @param array $cells    Cell data (formatting information and content)
     *
     * @return void
     */
    public function MultiRow($cells, $fill=false) {
        if (!is_array($cells) || !count($cells))
            return false;

        if($fill)
            $this->SetFillColor(250,250,250);

        $rowHeight = 0;
        foreach ($cells as $r) {
            $numLines = $this->getNumLines($r['value'], $r['width']);
            $_rh = $this->GetRowHeight($numLines);

            if ($_rh > $rowHeight)
                $rowHeight = $_rh;
        }

        $this->checkPageBreak($rowHeight);

        $page_start = $this->getPage();
        $page_ends = array();
        $y_start = $this->GetY();
        $y_ends = array();

        $farthest_page = $page_start;
        $maxy_per_page = array();

        $ln = 0;
        $last_item = end($cells);

        foreach ($cells as $r) {
            if ($r == $last_item)
                $ln = 1;

            $this->MultiCell($r['width'], $rowHeight, $r['value'], 0, $r['align'], $fill, $ln, $r['x']);

            $page_ends[] = $this->getPage();
            $y_ends[] = $this->GetY();

            if ($this->getPage() > $farthest_page)
                $farthest_page = $this->getPage();

            if (!isset($maxy_per_page[$farthest_page]) || $maxy_per_page[$farthest_page] < $this->GetY())
                $maxy_per_page[$farthest_page] = $this->GetY();

            $this->setPage($page_start);
        }

        $all_on_same_page = count(array_unique($page_ends)) == 1;

        if (max($page_ends) == $page_start)
            $ynew = max($y_ends);
        elseif ($all_on_same_page)
            $ynew = max($y_ends);
        else
            $ynew = $maxy_per_page[$farthest_page];

        $this->setPage(max($page_ends));
        $this->SetXY($this->GetX(),$ynew);
    }

    /**
     * Outputs a letterhead
     *
     * @return void
     */
    public function AddLetterhead($logofile=null) {
        if (is_null($logofile))
            if (strlen(PHPAGA_LETTERHEAD_LOGO_FILE))
                $logofile = PHPAGA_FILEPATH.'00/'.PHPAGA_LETTERHEAD_LOGO_FILE;

        if (strlen($logofile) && is_readable($logofile))
            $this->Image($logofile, 155, 5, 40, 0);

        $this->SetXY(20, 12);
        $this->SetFont($this->defaultFontName, '', 24);
        $this->MultiCell(0, 24, PHPAGA_LETTERHEAD_COMPANY_NAME, 0, 'L', 0, 1, 20);
        $this->SetFont($this->defaultFontName, '', 8);

        $this->HorizontalLine(0, 160, 25, array(255,0,0), 0.4);

        $this->SetXY(20, 26);
        $this->MultiCell(40, 10, PHPAGA_LETTERHEAD_COMPANY_STREET."\n".PHPAGA_LETTERHEAD_COMPANY_FULLLOCATION, 0, 'L', 0, 0);
        $this->MultiCell(40, 10, PHPAGA_LETTERHEAD_COMPANY_PHONE."\n".PHPAGA_LETTERHEAD_COMPANY_EMAIL, 0, 'L', 0, 0);
        $this->MultiCell(60, 10, PHPAGA_LETTERHEAD_OWNER_PERSONALTAXNR."\n".PHPAGA_LETTERHEAD_COMPANY_TAXNR, 0, 'L', 0, 1);
    }

    /**
     * Outputs the formatted name and address of a recipient.
     *
     * @param array $recipient    Recipient name and address
     *
     * @return void
     */
    public function AddRecipient($recipient) {
        $this->SetFont($this->defaultFontName, 'B', 10);
        $this->setXY(120, 45);
        $this->_recipient_top = $this->GetY();
        $this->MultiCell(0, 10, $recipient['name'], 0, 'L', 0, 0);

        $this->SetFont($this->defaultFontName, '', 10);
        $this->SetXY(120, 50);

        $recipient = implode("\n", $recipient['address']);
        $this->MultiCell(80, 10, $recipient, 0, 'L', 0, 1);
        $this->_recipient_bottom = $this->GetY();
    }
}


/**
 * This is the BillPDF class which custom classes can extend to achieve a 
 * different layouts. As BillPDF extends PhPagaPDF, methods like AddRecipient and 
 * AddLetterhead can be overwritten, too.
 */
class BillPDF extends PhPagaPDF {

    /**
     * Adds the billing plugin details (taxes and similar) of an invoice or a quotation.
     *
     * @param $details
     *
     * @return 
     */
    function AddDetails($details) {
        $_details = array();
        foreach ($details as $d) {
            if (strlen($d['text']) || strlen($d['amount']))
                $_details[] = $d;
            else
                $_details[] = SEPARATOR_LINE;
        }

        $this->AddTable(array(
            array('width' => 50, 'name' =>'text', 'title' => _('Text')),
            array('width' => 30, 'name' =>'amount', 'title' => _('Amount'), 'align' => 'R')),
            $_details, 
            array('headerPrint' => false, 'headerLine' => false, 'footerLine' => false));

        $this->Ln(4);
    }


    /**
     * Outputs the line items of an invoice or a quotation.
     *
     * @param array $lineitems    Array with line item information.
     *
     * @return void
     */
    function AddLineItems($lineitems) {
        $this->AddTable(array(
            array('width' => 30, 'name' =>'lit_prodcode', 'title' => _('Product code')),
            array('width' => 30, 'name' =>'lit_netprice_format', 'title' => _('Net price'), 'align' => 'R'),
            array('width' => 20, 'name' =>'lit_qty', 'title' => _('Qty./Hours'), 'align' => 'R'),
            array('width' => 80, 'name' =>'lit_desc', 'title' => _('Description'))
        ), 
        $lineitems,
        array('fill'=> true, 'title' => _('Line Items')));
    }


    /**
     * Outputs a task list.
     *
     * @param array $rows   Array with task rows
     *
     * @return void
     */
    public function AddTaskList($rows) {
        if (is_array($rows) && $rows) {
            if (is_array($rows) && count($rows)) {
                $this->AddTable(array(
                    array('width' => 40, 'name' => 'date', 'title' => _('Date')),
                    array('width' => 10, 'name' => 'op_duration_hrs', 'title' => _('Duration'), 'align' => 'R'),
                    array('width' => 30, 'name' => 'prj_title', 'title' => _('Project')),
                    array('width' => 30, 'name' => 'op_person', 'title' => _('Person')),
                    array('width' => 60, 'name' => 'op_desc', 'title' => _('Task'))),
                $rows, 
                array('title' => _('Tasks')));
            }
        }
    }

    /**
     * Outputs a time sheet.
     *
     * @param array $rows   Array with task rows
     *
     * @return void
     */
    public function AddTimeSheet($rows) {
        if (is_array($rows) && $rows) {
            if (is_array($rows) && count($rows)) {
                $this->AddTable(array(
                    array('width' => 40, 'name' => 'date', 'title' => _('Date')),
                    array('width' => 10, 'name' => 'op_duration_hrs', 'title' => _('Duration'), 'align' => 'R'),
                    array('width' => 50, 'name' => 'prj_title', 'title' => _('Project'))),
                $rows, 
                array('title' => _('Time sheet')));
            }
        }
    }

}


/**
 * The InvoicePDF class that all invoice PDF (template) classes should inherit from.
 */
class InvoicePDF extends BillPDF {

    /**
     * Outputs an invoice with its core data, details, line items, expenses, 
     * payments and late fees.
     *
     * @param array $data   Invoice data
     *
     * @return void
     */
    public function AddBody($data) {
        $this->SetXY(20, $this->_recipient_top);
        $this->SetFont($this->defaultFontName, '', 16);
        $this->Cell(80, 0, _('Invoice').' '.$data['bill_number'], 0, 1);
        $this->SetFont($this->defaultFontName, '', 10);
        $this->MultiCell(30, 0, _('Date'), 0, 'L', 0, 0, 20);
        $this->MultiCell(30, 0,date(PHPAGA_DATEFORMAT_PRINT, strtotime($data['bill_date'])), 0, 'L', 0, 1);
        if (strlen($data['bill_date_due'])) {
            $this->MultiCell(30, 0, _('Date due'), 0, 'L', 0, 0, 20);
            $this->MultiCell(30, 0,date(PHPAGA_DATEFORMAT_PRINT, strtotime($data['bill_date_due'])), 0, 'L', 0, 1);
        }

        if (is_numeric($this->_recipient_bottom) && ($this->_recipient_bottom > $this->GetY()))
            $this->SetY($this->_recipient_bottom + 10);
        else
            $this->setY(20, $this->GetY()+5);

        if (isset($data['bill_additional_line']) && strlen($data['bill_additional_line'])) {
            $this->setX(20);
            $this->SetFont($this->defaultFontName, '', 10);
            $this->MultiCell(175, 10, $data['bill_additional_line'], 0, 'L', 0, 1, '', '', true, null, true);
        }

        $this->AddDetails($data['bill_details']);

        if (isset($data['lineitems']) && $data['lineitems'])
            $this->AddLineItems($data['lineitems']);

        if (isset($data['expenses']) && $data['expenses']) {
            $this->AddTable(array(
                array('width' => 20, 'name' =>'exp_date', 'title' => _('Date'), 'type' => 'date'),
                array('width' => 20, 'name' =>'exp_sum', 'title' => _('Amount'), 'type' => 'amount'), 
                array('width' => 10, 'name' =>'curr_name', 'title' => ''),
                array('width' => 30, 'name' =>'prj_title', 'title' => _('Project')),
                array('width' => 80, 'name' =>'exp_desc', 'title' => _('Description'))
            ),
            $data['expenses'],
            array('fill'=> true, 'title' => _('Reimbursable expenses')));
        }

        if (isset($data['payments']) && $data['payments']) {
            $this->AddTable(array(
                array('width' => 20, 'name' =>'pmt_date', 'title' => _('Date'), 'type' => 'date'),
                array('width' => 20, 'name' =>'pmt_amt', 'title' => _('Amount'), 'type' => 'amount'), 
                array('width' => 10, 'name' =>'curr_name', 'title' => ''),
                array('width' => 80, 'name' =>'pmt_note', 'title' => _('Note'))
            ),
            $data['payments'],
            array('fill'=> true, 'title' => _('Payments')));
        }

        if (isset($data['latefees']) && $data['latefees']) {
            $this->AddTable(array(
                array('width' => 20, 'name' =>'fee_date', 'title' => _('Date'), 'type' => 'date'),
                array('width' => 20, 'name' =>'fee_amt', 'title' => _('Amount'), 'type' => 'amount'), 
            ),
            $data['latefees'],
            array('fill'=> true, 'title' => _('Late Fees')));
        }
    }
}


/**
 * The QuotationPDF class that all invoice PDF (template) classes should inherit from.
 */
class QuotationPDF extends BillPDF {

    /**
     * Outputs a quotation with its core data, details and line items.
     *
     * @param array $data   Quotation data
     *
     * @return void
     */
    public function AddBody($data) {
        $this->SetXY(20, $this->_recipient_top);
        $this->SetFont($this->defaultFontName, '', 16);
        $this->Cell(80, 0, _('Quotation').' '.$data['quot_number'], 0, 1);
        $this->SetFont($this->defaultFontName, '', 10);
        $this->MultiCell(30, 0, _('Date'), 0, 'L', 0, 0, 20);
        $this->MultiCell(30, 0,date(PHPAGA_DATEFORMAT_PRINT, strtotime($data['quot_date'])), 0, 'L', 0, 1);

        if (is_numeric($this->_recipient_bottom) && ($this->_recipient_bottom > $this->GetY()))
            $this->SetY($this->_recipient_bottom + 10);
        else
            $this->setY(20, $this->GetY()+5);

        if (isset($data['quot_additional_line']) && strlen($data['quot_additional_line'])) {
            $this->setX(20);
            $this->SetFont($this->defaultFontName, '', 10);
            $this->MultiCell(175, 10, $data['quot_additional_line'], 0, 'L', 0, 1, '', '', true, null, true);
        }

        $this->AddDetails($data['quot_details']);
        
        if (isset($data['lineitems']) && $data['lineitems'])
            $this->AddLineItems($data['lineitems']);
    }
}

phpaga_load_plugins('pdf');

?>
