<?php
/**
 * This file is part of PHPWord - A pure PHP library for reading and writing
 * word processing documents.
 *
 * PHPWord is free software distributed under the terms of the GNU Lesser
 * General Public License version 3 as published by the Free Software Foundation.
 *
 * For the full copyright and license information, please read the LICENSE
 * file that was distributed with this source code. For the full list of
 * contributors, visit https://github.com/PHPOffice/PHPWord/contributors.
 *
 * @see         https://github.com/PHPOffice/PHPWord
 *
 * @license     http://www.gnu.org/licenses/lgpl.txt LGPL version 3
 */

namespace PhpOffice\PhpWord\Reader;

use Exception;
use PhpOffice\PhpWord\Element\AbstractElement;
use PhpOffice\PhpWord\PhpWord;
use PhpOffice\PhpWord\Reader\Word2007\AbstractPart;
use PhpOffice\PhpWord\Shared\XMLReader;
use PhpOffice\PhpWord\Shared\ZipArchive;

/**
 * Reader for Word2007.
 *
 * @since 0.8.0
 *
 * @todo watermark, checkbox, toc
 * @todo Partly done: image, object
 */
class Word2007 extends AbstractReader implements ReaderInterface
{
    /**
     * Loads PhpWord from file.
     *
     * @param string $docFile
     *
     * @return \PhpOffice\PhpWord\PhpWord
     */
    public function load($docFile)
    {
        $phpWord = new PhpWord();
        $relationships = $this->readRelationships($docFile);
        $commentRefs = [];

        $steps = [
            [
                'stepPart' => 'document',
                'stepItems' => [
                    'styles' => 'Styles',
                    'numbering' => 'Numbering',
                ],
            ],
            [
                'stepPart' => 'main',
                'stepItems' => [
                    'officeDocument' => 'Document',
                    'core-properties' => 'DocPropsCore',
                    'extended-properties' => 'DocPropsApp',
                    'custom-properties' => 'DocPropsCustom',
                ],
            ],
            [
                'stepPart' => 'document',
                'stepItems' => [
                    'endnotes' => 'Endnotes',
                    'footnotes' => 'Footnotes',
                    'settings' => 'Settings',
                    'comments' => 'Comments',
                ],
            ],
        ];

        foreach ($steps as $step) {
            $stepPart = $step['stepPart'];
            $stepItems = $step['stepItems'];
            if (!isset($relationships[$stepPart])) {
                continue;
            }
            foreach ($relationships[$stepPart] as $relItem) {
                $relType = $relItem['type'];
                if (isset($stepItems[$relType])) {
                    $partName = $stepItems[$relType];
                    $xmlFile = $relItem['target'];
                    $part = $this->readPart($phpWord, $relationships, $commentRefs, $partName, $docFile, $xmlFile);
                    $commentRefs = $part->getCommentReferences();
                }
            }
        }

        return $phpWord;
    }

    /**
     * Read document part.
     *
     * @param array<string, array<string, null|AbstractElement>> $commentRefs
     */
    private function readPart(PhpWord $phpWord, array $relationships, array $commentRefs, string $partName, string $docFile, string $xmlFile): AbstractPart
    {
        $partClass = "PhpOffice\\PhpWord\\Reader\\Word2007\\{$partName}";
        if (!class_exists($partClass)) {
            throw new Exception(sprintf('The part "%s" doesn\'t exist', $partClass));
        }

        /** @var AbstractPart $part Type hint */
        $part = new $partClass($docFile, $xmlFile);
        $part->setImageLoading($this->hasImageLoading());
        $part->setRels($relationships);
        $part->setCommentReferences($commentRefs);
        $part->read($phpWord);

        return $part;
    }

    /**
     * Read all relationship files.
     *
     * @param string $docFile
     *
     * @return array
     */
    private function readRelationships($docFile)
    {
        $relationships = [];

        // _rels/.rels
        $relationships['main'] = $this->getRels($docFile, '_rels/.rels');

        // word/_rels/*.xml.rels
        $wordRelsPath = 'word/_rels/';
        $zip = new ZipArchive();
        if ($zip->open($docFile) === true) {
            for ($i = 0; $i < $zip->numFiles; ++$i) {
                $xmlFile = $zip->getNameIndex($i);
                if ((substr($xmlFile, 0, strlen($wordRelsPath))) == $wordRelsPath && (substr($xmlFile, -1)) != '/') {
                    $docPart = str_replace('.xml.rels', '', str_replace($wordRelsPath, '', $xmlFile));
                    $relationships[$docPart] = $this->getRels($docFile, $xmlFile, 'word/');
                }
            }
            $zip->close();
        }

        return $relationships;
    }

    /**
     * Get relationship array.
     *
     * @param string $docFile
     * @param string $xmlFile
     * @param string $targetPrefix
     *
     * @return array
     */
    private function getRels($docFile, $xmlFile, $targetPrefix = '')
    {
        $metaPrefix = 'http://schemas.openxmlformats.org/package/2006/relationships/metadata/';
        $officePrefix = 'http://schemas.openxmlformats.org/officeDocument/2006/relationships/';

        $rels = [];

        $xmlReader = new XMLReader();
        $xmlReader->getDomFromZip($docFile, $xmlFile);
        $nodes = $xmlReader->getElements('*');
        foreach ($nodes as $node) {
            $rId = $xmlReader->getAttribute('Id', $node);
            $type = $xmlReader->getAttribute('Type', $node);
            $target = $xmlReader->getAttribute('Target', $node);
            $mode = $xmlReader->getAttribute('TargetMode', $node);

            // Remove URL prefixes from $type to make it easier to read
            $type = str_replace($metaPrefix, '', $type);
            $type = str_replace($officePrefix, '', $type);
            $docPart = str_replace('.xml', '', $target);

            // Do not add prefix to link source
            if ($type != 'hyperlink' && $mode != 'External') {
                $target = $targetPrefix . $target;
            }

            // Push to return array
            $rels[$rId] = ['type' => $type, 'target' => $target, 'docPart' => $docPart, 'targetMode' => $mode];
        }
        ksort($rels);

        return $rels;
    }
}
