<?php namespace Williamson\Laragram;

/**
 * php-client for telegram-cli
 */
class TgCommands extends AbstractWrapperCommands
{

    /**
     * Send a broadcast message to several users at once.
     * @param array $peers
     * @param       $msg
     * @return bool|string
     */
    public function broadcastMsg(array $peers, $msg)
    {
        $peerList = $this->formatPeers($peers);

        return $this->exec('broadcast ' . $peerList . ' ' . $msg);
    }

    /**
     * Add a $peer to a group chat. Sends him the last $msgToForward messages from this chat
     *
     * @param        $chat
     * @param string $peer
     * @param int    $msgToForward
     * @return mixed
     */
    public function chatAddUser($chat, $peer, $msgToForward = 100)
    {
        $chat = $this->escapePeer($chat); //Not escapeStringArgument as chat needs underscores if spaces in name
        $peer = $this->escapePeer($peer);

        return $this->exec('chat_add_user ' . $chat . ' ' . $peer . ' ' . $msgToForward);
    }

    /**
     * Create a group chat with $peers
     *
     * @param string $groupName The name of the group chat
     * @param array  $peers     All peers to be included in chat
     * @return bool|string
     */
    public function chatCreateGroup($groupName, array $peers)
    {
        $groupName = $this->escapeStringArgument($groupName);
        $peerList  = $this->formatPeers($peers);

        return $this->exec('create_group_chat ' . $groupName . ' ' . $peerList);
    }

    /**
     * Create a secret chat with $peer
     *
     * @param $peer
     * @return bool|string
     */
    public function chatCreateSecret($peer)
    {
        $peer = $this->escapePeer($peer);

        return $this->exec('create_secret_chat ' . $peer);
    }

    /**
     * Deletes $peer from $chat
     *
     * @param        $chat
     * @param string $peer
     * @return mixed
     */
    public function chatDelUser($chat, $peer)
    {
        $chat = $this->escapePeer($chat); //Not escapeStringArgument as chat needs underscores if spaces in name
        $peer = $this->escapePeer($peer);

        return $this->exec('chat_del_user ' . $chat . ' ' . $peer);
    }

    /**
     * Return chat link that can be used to join a chat
     * @param $chatNameOrId
     * @return bool|string
     */
    public function chatExportLink($chatNameOrId)
    {
        $chat = $this->escapePeer($chatNameOrId); //Not escapeStringArgument as chat needs underscores if spaces in name

        return $this->exec('export_chat_link ' . $chat);
    }

    /**
     * Get info about a chat $chat
     * @param $chat
     * @return bool|string
     */
    public function chatInfo($chat)
    {
        $chat = $this->escapePeer($chat); //Not escapeStringArgument as chat needs underscores if spaces in name

        return $this->exec('chat_info ' . $chat);
    }

    /**
     * Rename $chat to $newChatName
     * @param $chat
     * @param $newChatName
     * @return bool|string
     */
    public function chatRename($chat, $newChatName)
    {
        $chat        = $this->escapePeer($chat); //Not escapeStringArgument as chat needs underscores if spaces in name
        $newChatName = $this->escapeStringArgument($newChatName); //Not escapeStringArgument as chat needs underscores if spaces in name

        return $this->exec('rename_chat ' . $chat . ' ' . $newChatName);
    }

    /**
     * Sets the group chat picture for specified $chat
     *
     * The photo will be cropped to square
     *
     * @param        $chat
     * @param string $mediaUri Either a URL or a local filename of the photo you wish to set
     * @return bool|string
     * @uses     exec()
     */
    public function chatSetPhoto($chat, $mediaUri)
    {
        $chat = $this->escapePeer($chat); //Not escapeStringArgument as chat needs underscores if spaces in name

        //Process the requested media file.
        $processedMedia = $this->processMediaUri($mediaUri);
        if ( ! $processedMedia) {
            return false;
        }

        //Send media file.
        $result = $this->exec('chat_set_photo ' . $chat . ' ' . $processedMedia['filepath']);

        //Clean up if media file came from REMOTE address
        $this->cleanUpMedia($processedMedia);

        return $result;
    }

    /**
     * Adds a user to the contact list
     *
     * @param int|string $phoneNumber The phone-number of the new contact, needs to be a telegram-user.
     *                                Can start with or without '+'.
     * @param string     $firstName   The first name of the new contact
     * @param string     $lastName    The last name of the new contact
     *
     * @return string|boolean The new contact "$firstName $lastName"; false if somethings goes wrong
     *
     * @uses exec()
     * @uses escapeStringArgument()
     */
    public function contactAdd($phoneNumber, $firstName, $lastName)
    {
        $phoneNumber = $this->formatPhoneNumber($phoneNumber);

        return $this->exec('add_contact ' . $phoneNumber . ' ' . $this->escapeStringArgument($firstName)
            . ' ' . $this->escapeStringArgument($lastName));
    }

    /**
     * Deletes a contact.
     *
     * @param string $contact The contact, gets escaped with escapePeer(),
     *                        so you can directly use the values from getContactList()
     *
     * @return boolean true on success, false otherwise
     *
     * @uses exec()
     * @uses escapePeer()
     */
    public function contactDelete($contact)
    {
        return $this->exec('del_contact ' . $this->escapePeer($contact));
    }

    /**
     * Gets the users contact list
     *
     * @return bool|string
     */
    public function contactList()
    {
        return explode(PHP_EOL, $this->exec('contact_list'));
    }

    /**
     * Renames a user in the contact list
     *
     * @param string $contact   The contact, gets escaped with escapePeer(),
     *                          so you can directly use the values from getContactList()
     * @param string $firstName The new first name for the contact
     * @param string $lastName  The new last name for the contact
     *
     * @return string|boolean The renamed contact "$firstName $lastName"; false if somethings goes wrong
     *
     * @uses exec()
     * @uses escapeStringArgument()
     */
    public function contactRename($contact, $firstName, $lastName)
    {
        $contact   = $this->escapePeer($contact);
        $firstName = $this->escapeStringArgument($firstName);
        $lastName  = $this->escapeStringArgument($lastName);

        return $this->exec('rename_contact ' . $contact . ' ' . $firstName . ' ' . $lastName);
    }

    /**
     * Delete a message with ID of $msgId
     * @param $msgId
     * @return bool|string
     */
    public function deleteMsg($msgId)
    {
        return $this->exec('delete_msg ' . $msgId);
    }

    /**
     * Return the export card for the user
     *
     * @return bool|string
     */
    public function exportCard()
    {
        return $this->exec('export_card');
    }

    /**
     * Returns an array of all contacts in form of "[firstName] [lastName]".
     *
     * @return array|boolean An array with your contacts; false if somethings goes wrong
     *
     * @uses exec()
     */
    public function getContactList()
    {
        return explode(PHP_EOL, $this->exec('contact_list'));
    }

    /**
     * Returns an array of all your dialogs in form of
     * "User [firstName] [lastName]: [number of unread messages] unread"  Or
     * "Chat [group chat name]: [number of unread messages] unread"
     *
     * Will get better formatted in the future.
     *
     * @return array|boolean An array with your dialogs; false if somethings goes wrong
     *
     * @uses exec()
     */
    public function getDialogList()
    {
        return explode(PHP_EOL, $this->exec('dialog_list'));
    }

    /**
     * Executes the history-command and returns the answer (the answer is un-formatted right now).
     * Will get better formatted in the future.
     *
     * @param string $peer   The peer, gets escaped with escapePeer(),
     *                       so you can directly use the values from getContactList()
     * @param int    $limit  (optional) Limit answer to $limit messages. If not set, there is no limit.
     * @param int    $offset (optional) Use this with the $limit parameter to go through older messages.
     *                       Can also be negative.
     *
     * @return string|boolean The answer of the history-command; false if somethings goes wrong
     *
     * @uses exec()
     * @uses escapePeer()
     *
     * @see  https://core.telegram.org/method/messages.getHistory
     */
    public function getHistory($peer, $limit = null, $offset = null)
    {
        if ($limit !== null) {
            $limit = (int) $limit;
            if ($limit < 1) { //if limit is lesser than 1, telegram-cli crashes
                $limit = 1;
            }
            $limit = ' ' . $limit;
        } else {
            $limit = '';
        }
        if ($offset !== null) {
            $offset = ' ' . (int) $offset;
        } else {
            $offset = '';
        }

        return $this->exec('history ' . $this->escapePeer($peer) . $limit . $offset);
    }

    /**
     * Executes the user_info-command and returns it answer (the answer is unformated right now).
     * Will get better formated in the future.
     *
     * @param string $user The user, gets escaped with escapePeer(),
     *                     so you can directly use the values from getContactList()
     *
     * @return string|boolean The answer of the user_info-command; false if somethings goes wrong
     *
     * @uses exec()
     * @uses escapePeer()
     */
    public function getUserInfo($user)
    {
        return $this->exec('user_info ' . $this->escapePeer($user));
    }

    /**
     * Marks all messages with $peer as read.
     *
     * @param string $peer The peer, gets escaped with escapePeer(),
     *                     so you can directly use the values from getContactList()
     *
     * @return boolean true on success, false otherwise
     *
     * @uses exec()
     * @uses escapePeer()
     */
    public function markRead($peer)
    {
        return $this->exec('mark_read ' . $this->escapePeer($peer));
    }

    /**
     * Alias function for sendMsg
     * @param string $peer
     * @param string $msg
     * @return bool
     */
    public function msg($peer, $msg)
    {
        return $this->sendMsg($peer, $msg);
    }

    /**
     * Sends a Audio file to $peer
     *
     * @param string $peer
     * @param string $mediaUri Either a URL or a local filename of the audio you wish to send
     * @param bool   $cleanUp
     * @return bool
     * @uses exec()
     * @uses escapePeer()
     * @uses escapeStringArgument()
     */
    public function sendAudio($peer, $mediaUri, $cleanUp = false)
    {
        $peer = $this->escapePeer($peer);

        //Process the requested media file.
        $processedMedia = $this->processMediaUri($mediaUri);
        if ( ! $processedMedia) {
            return false;
        }

        //Send media file.
        $result = $this->exec('send_audio ' . $peer . ' ' . $processedMedia['filepath']);

        if ($cleanUp) {
            //Clean up if media file came from REMOTE address
            $this->cleanUpMedia($processedMedia);
        }

        return $result;
    }

    /**
     * Sends contact to $peer (not necessary telegram user)
     * @param string $peer
     * @param string $phoneNumber in format
     * @param string $firstName
     * @param string $lastName
     * @return mixed
     */
    public function sendContact($peer, $phoneNumber, $firstName, $lastName)
    {
        $peer        = $this->escapePeer($peer);
        $phoneNumber = $this->formatPhoneNumber($phoneNumber);
        $firstName   = $this->escapeStringArgument($firstName);
        $lastName    = $this->escapeStringArgument($lastName);

        return $this->exec('send_contact  ' . $peer . ' ' . $phoneNumber . ' ' . $firstName . ' ' . $lastName);
    }

    /**
     * Sends a Document to $peer
     *
     * @param string $peer
     * @param string $mediaUri Either a URL or a local filename of the media you wish to send
     * @param bool   $cleanUp
     * @return bool
     * @uses exec()
     * @uses escapePeer()
     * @uses escapeStringArgument()
     */
    public function sendDocument($peer, $mediaUri, $cleanUp = false)
    {
        $peer = $this->escapePeer($peer);

        //Process the requested media file.
        $processedMedia = $this->processMediaUri($mediaUri);
        if ( ! $processedMedia) {
            return false;
        }

        //Send media file.
        $result = $this->exec('send_document ' . $peer . ' ' . $processedMedia['filepath']);

        if ($cleanUp) {
            //Clean up if media file came from REMOTE address
            $this->cleanUpMedia($processedMedia);
        }

        return $result;
    }

    /**
     * Sends a map of the supplied lat/long coordinated to $peer
     *
     * @param string $peer
     * @param string $latitude  in decimal format up to 6 decimal places. Eg: 37.018757
     * @param string $longitude in decimal format up to 6 decimal places. Eg: -7.965297
     *
     * @uses exec()
     * @uses escapePeer()
     *
     * @return mixed
     */
    public function sendLocation($peer, $latitude, $longitude)
    {
        $latitude  = $this->formatCoordinate($latitude);
        $longitude = $this->formatCoordinate($longitude);
        $peer      = $this->escapePeer($peer);

        return $this->exec('send_location  ' . $peer . ' ' . $latitude . ' ' . $longitude);
    }

    /**
     * Sends a text message to $peer.
     *
     * @param string $peer The peer, gets escaped with escapePeer(),
     *                     so you can directly use the values from getContactList()
     * @param string $msg  The message to send, gets escaped with escapeStringArgument()
     *
     * @return boolean true on success, false otherwise
     *
     * @uses exec()
     * @uses escapePeer()
     * @uses escapeStringArgument()
     */
    public function sendMsg($peer, $msg)
    {
        $peer = $this->escapePeer($peer);
        $msg  = $this->escapeStringArgument($msg);

        return $this->exec('msg ' . $peer . ' ' . $msg);
    }

    /**
     * Sends a Photo to $peer
     *
     * @param string $peer
     * @param string $mediaUri Either a URL or a local filename of the image you wish to send
     *
     * @param bool   $cleanUp
     * @return bool
     * @uses exec()
     * @uses escapePeer()
     * @uses escapeStringArgument()
     */
    public function sendPhoto($peer, $mediaUri, $cleanUp = false)
    {
        $peer = $this->escapePeer($peer);

        //Process the requested media file.
        $processedMedia = $this->processMediaUri($mediaUri);
        if ( ! $processedMedia) {
            return false;
        }

        //Send media file.
        $result = $this->exec('send_photo ' . $peer . ' ' . $processedMedia['filepath']);

        if ($cleanUp) {
            //Clean up if media file came from REMOTE address
            $this->cleanUpMedia($processedMedia);
        }

        return $result;
    }

    /**
     * Sends the **contents** of a text file to $peer as plain text message
     *
     * @param string $peer
     * @param string $mediaUri Either a URL or a local filename of the text file you wish to send
     *
     * @param bool   $cleanUp
     * @return bool
     * @uses exec()
     * @uses escapePeer()
     * @uses escapeStringArgument()
     */
    public function sendText($peer, $mediaUri, $cleanUp = false)
    {
        $peer = $this->escapePeer($peer);

        //Process the requested media file.
        $processedMedia = $this->processMediaUri($mediaUri);
        if ( ! $processedMedia) {
            return false;
        }

        //Send media file.
        $result = $this->exec('send_text ' . $peer . ' ' . $processedMedia['filepath']);

        if ($cleanUp) {
            //Clean up if media file came from REMOTE address
            $this->cleanUpMedia($processedMedia);
        }

        return $result;
    }

    /**
     * Send the typing status to $peer
     *
     * @param string $peer
     *
     * @uses escapePeer()
     * @return mixed
     */
    public function sendTypingStart($peer)
    {
        $peer = $this->escapePeer($peer);

        return $this->exec('send_typing ' . $peer);
    }

    /**
     * Stop the typing status to $peer
     *
     * @param string $peer
     *
     * @uses escapePeer()
     * @return mixed
     */
    public function sendTypingStop($peer)
    {
        $peer = $this->escapePeer($peer);

        return $this->exec('send_typing_abort ' . $peer);
    }

    /**
     * Sends a Video to $peer
     *
     * @param string $peer
     * @param string $mediaUri Either a URL or a local filename of the video you wish to send
     * @param bool   $cleanUp
     * @return bool
     * @uses exec()
     * @uses escapePeer()
     * @uses escapeStringArgument()
     */
    public function sendVideo($peer, $mediaUri, $cleanUp = false)
    {
        $peer = $this->escapePeer($peer);

        //Process the requested media file.
        $processedMedia = $this->processMediaUri($mediaUri);
        if ( ! $processedMedia) {
            return false;
        }

        //Send media file.
        $result = $this->exec('send_video ' . $peer . ' ' . $processedMedia['filepath']);

        if ($cleanUp) {
            //Clean up if media file came from REMOTE address
            $this->cleanUpMedia($processedMedia);
        }

        return $result;
    }

    /**
     * Sets the logged in users profile name
     *
     * @param string $firstName The new first name for the profile
     * @param string $lastName  The new last name for the profile
     *
     * @uses exec()
     * @uses escapeStringArgument()
     * @return string|boolean
     */
    public function setProfileName($firstName, $lastName)
    {
        return $this->exec('set_profile_name ' . $this->escapeStringArgument($firstName) . ' ' . $this->escapeStringArgument($lastName));
    }

    /**
     * Sets the profile picture for the logged in user
     *
     * The photo will be cropped to square
     *
     * @param string $mediaUri Either a URL or a local filename of the photo you wish to set
     *
     * @uses     exec()
     * @return bool|string
     */
    public function setProfilePhoto($mediaUri)
    {
        //Process the requested media file.
        $processedMedia = $this->processMediaUri($mediaUri);
        if ( ! $processedMedia) {
            return false;
        }

        //Send media file.
        $result = $this->exec('set_profile_photo ' . $processedMedia['filepath']);

        //Clean up if media file came from REMOTE address
        $this->cleanUpMedia($processedMedia);

        return $result;
    }

    /**
     * Sets status as offline.
     *
     * @return boolean true on success, false otherwise
     *
     * @uses exec()
     */
    public function setStatusOffline()
    {
        return $this->exec('status_offline');
    }

    /**
     * Sets status as online.
     *
     * @return boolean true on success, false otherwise
     *
     * @uses exec()
     */
    public function setStatusOnline()
    {
        return $this->exec('status_online');
    }

    /**
     * Sets the username for the logged in User
     * Must be a minimum of 5 characters.
     *
     * @param string $username
     * @return mixed
     */
    public function setUsername($username)
    {
        return $this->exec('set_username ' . $username);
    }

    /**
     * Check that the URL given actually exists and is resolvable and that
     * the file located there is within size limits.
     *
     * What are the size limits? I dunno!
     *
     * @param string $fileUri
     * @param array  $mediaFileInfo
     * @return bool|array
     */
    protected function checkUrlExistsAndSize($fileUri, array $mediaFileInfo)
    {
        $mediaFileInfo['url'] = $fileUri;
        //File is a URL. Create a curl connection but DON'T download the body content
        //because we want to see if file is too big.
        $curl = curl_init();
        curl_setopt($curl, CURLOPT_URL, "$fileUri");
        curl_setopt($curl, CURLOPT_USERAGENT,
            "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.11) Gecko/20071127 Firefox/2.0.0.11");
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($curl, CURLOPT_HEADER, false);
        curl_setopt($curl, CURLOPT_NOBODY, true);

        if (curl_exec($curl) === false) {
            return false;
        }

        //While we're here, get mime type and filesize and extension
        $info                           = curl_getinfo($curl);
        $mediaFileInfo['filesize']      = $info['download_content_length'];
        $mediaFileInfo['filemimetype']  = $info['content_type'];
        $mediaFileInfo['fileextension'] = pathinfo(parse_url($mediaFileInfo['url'], PHP_URL_PATH), PATHINFO_EXTENSION);
        curl_close($curl);

        return $mediaFileInfo;
    }

    /**
     * Clean up any temp files created if media file came from REMOTE address (eg URL)
     * @param array $processedMedia
     */
    protected function cleanUpMedia(array $processedMedia)
    {
        if (isset($processedMedia['url']) && file_exists($processedMedia['filepath'])) {
            unlink($processedMedia['filepath']);
        }
    }

    /**
     * Determine if we can use the filename given to us via a URI or do
     * we have to create an unique one in the system folder.
     *
     * @param string $originalFilename
     * @param array  $mediaFileInfo
     * @return mixed
     */
    protected function determineFilename($originalFilename, array $mediaFileInfo)
    {
        if (is_null($originalFilename) || ! isset($originalFilename) || is_file(sys_get_temp_dir() . '/' . $originalFilename)) {
            //Need to create a unique file name as file either exists or we couldn't determine it.
            //Create temp file in system folder.
            $uniqueFilename = tempnam(sys_get_temp_dir(), 'tg');
            //Add file extension
            rename($uniqueFilename, $uniqueFilename . '.' . $mediaFileInfo['fileextension']);

            $mediaFileInfo['filepath'] = $uniqueFilename . '.' . $mediaFileInfo['fileextension'];

        } else {
            $mediaFileInfo['filepath'] = sys_get_temp_dir() . '/' . $originalFilename;
        }

        return $mediaFileInfo;
    }

    /**
     * Download the file from the URL provided.
     *
     * @param string $fileUri
     * @param string $tempFileName
     */
    protected function downloadMediaFileFromURL($fileUri, $tempFileName)
    {
        $curl = curl_init();
        curl_setopt($curl, CURLOPT_URL, "$fileUri");
        curl_setopt($curl, CURLOPT_USERAGENT,
            "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.8.1.11) Gecko/20071127 Firefox/2.0.0.11");
        curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
        curl_setopt($curl, CURLOPT_HEADER, false);
        curl_setopt($curl, CURLOPT_NOBODY, false);
        curl_setopt($curl, CURLOPT_BUFFERSIZE, 1024);
        curl_setopt($curl, CURLOPT_FILE, $tempFileName);
        curl_exec($curl);
        curl_close($curl);
    }

    /**
     * Formats the coordinates given to a max of 6 decimal places.
     * @param $coordinate
     * @return float
     */
    protected function formatCoordinate($coordinate)
    {
        return (float) round(preg_replace("/[^0-9.-]/", "", $coordinate), 6);
    }

    /**
     * @param $phoneNumber
     * @return int|string
     */
    protected function formatPhoneNumber($phoneNumber)
    {
        $phoneNumber = preg_replace("/[^0-9]/", "", $phoneNumber);

        if ($phoneNumber[0] != 0) {
            return ('+' . $phoneNumber);
        }

        return $phoneNumber;
    }

    /**
     * Takes a URI (in the form of a URL or local file path) and determines if
     * the file exists and that it is not too big. If the file is remote (ie a URL)
     * it will download the media file to the system temp directory for use.
     *
     * @param string $fileUri
     * @param int    $maxsizebytes
     * @return array|bool
     */
    protected function processMediaUri($fileUri, $maxsizebytes = 10485760)
    {
        //Setup the mediafile Array to contain all the file's info.
        $mediaFileInfo = array();

        if (filter_var($fileUri, FILTER_VALIDATE_URL) !== false) {
            //The URI provided was a URL. Lets check to see if it exists.
            $mediaFileInfo = $this->checkUrlExistsAndSize($fileUri, $mediaFileInfo);

            if ( ! $mediaFileInfo || $mediaFileInfo['filesize'] > $maxsizebytes) {
                //File too big. Or doesn't exist. Don't Download.
                return false;
            }

            //Have we already downloaded this file before? If so, we should just reuse it!
            $originalFilename = pathinfo($fileUri, PATHINFO_BASENAME);
            $fileOnDisk       = sys_get_temp_dir() . '/' . $originalFilename;

            if (file_exists($fileOnDisk) && (filesize($fileOnDisk) == $mediaFileInfo['filesize'])) {
                $mediaFileInfo['filepath'] = $fileOnDisk;

                //File is identical (most likely) no need to redownload it.
                return $mediaFileInfo;
            };

            //So either file doesn't exist on local drive or a file with a different filesize is already there.
            $mediaFileInfo = $this->determineFilename($originalFilename, $mediaFileInfo);

            //Save to a new temp file name
            $tempFileName = fopen($mediaFileInfo['filepath'], 'w');
            if ($tempFileName) {
                $this->downloadMediaFileFromURL($fileUri, $tempFileName);
                fclose($tempFileName);
            } else {
                unlink($mediaFileInfo['filepath']);

                return false;
            }

            //Success! We now have the file locally on our system to use.
            return $mediaFileInfo;

        } else {
            if (is_file($fileUri)) {
                //URI given was a local file name.
                $mediaFileInfo['filesize'] = filesize($fileUri);
                if ($mediaFileInfo['filesize'] > $maxsizebytes) {
                    //File too big
                    return false;
                }
                $mediaFileInfo['filepath']      = $fileUri;
                $mediaFileInfo['fileextension'] = pathinfo($fileUri, PATHINFO_EXTENSION);

//                $mediaFileInfo['filemimetype']  = get_mime($filepath);

                return $mediaFileInfo;
            }
        }

        //Couldn't tell what file was, local or URL.
        return false;
    }

    /**
     * Formats an array of peers so they can be used with group chats
     *
     * @param array $peers
     * @return string
     */
    private function formatPeers(array $peers)
    {
        $peers = array_map(array($this, 'escapePeer'), $peers);

        return implode(' ', $peers);
    }
}


<?php

namespace Boedah\Robo\Task\Drush;

use Robo\Common\CommandArguments;
use Robo\Task\CommandStack;

/**
 * Runs Drush commands in stack. You can use `stopOnFail()` to point that stack should be terminated on first fail.
 * You can define global options for all commands (like Drupal root and uri).
 * The option -y is always set, as it makes sense in a task runner.
 *
 * ``` php
 * $this->taskDrushStack()
 *     ->drupalRootDirectory('/var/www/html/some-site')
 *     ->uri('sub.example.com')
 *     ->maintenanceOn()
 *     ->updateDb()
 *     ->revertAllFeatures()
 *     ->maintenanceOff()
 *     ->run();
 * ```
 *
 * Example site install command:
 *
 * ``` php
 * $this->taskDrushStack()
 *   ->siteName('Site Name')
 *   ->siteMail('site-mail@example.com')
 *   ->locale('de')
 *   ->accountMail('mail@example.com')
 *   ->accountName('admin')
 *   ->accountPass('pw')
 *   ->dbPrefix('drupal_')
 *   ->sqliteDbUrl('sites/default/.ht.sqlite')
 *   ->disableUpdateStatusModule()
 *   ->siteInstall('minimal')
 *   ->run();
 * ```
 */
class DrushStack extends CommandStack
{
    use CommandArguments;

    protected $argumentsForNextCommand;

    /**
     * Drush site alias.
     * We need to save this, since it needs to be the first argument.
     *
     * @var string
     */
    protected $siteAlias;

    /**
     * @var string
     */
    protected $drushVersion;

    public function __construct($pathToDrush = 'drush')
    {
        $this->executable = $pathToDrush;
    }

    public function drupalRootDirectory($drupalRootDirectory)
    {
        $this->printTaskInfo('Drupal root: <info>' . $drupalRootDirectory . '</info>');
        $this->option('-r', $drupalRootDirectory);

        return $this;
    }

    public function uri($uri)
    {
        $this->printTaskInfo('URI: <info>' . $uri . '</info>');
        $this->option('-l', $uri);

        return $this;
    }

    public function siteAlias($alias)
    {
        $this->printTaskInfo('Site Alias: <info>' . $alias . '</info>');
        $this->siteAlias = $alias;

        return $this;
    }

    public function debug()
    {
        $this->option('-d');

        return $this;
    }

    public function verbose()
    {
        $this->option('-v');

        return $this;
    }

    public function simulate()
    {
        $this->option('-s');

        return $this;
    }

    public function siteName($siteName)
    {
        $this->argForNextCommand('--site-name=' . escapeshellarg($siteName));

        return $this;
    }

    /**
     * Add an argument used in the next invocation of drush.
     *
     * @param string $arg
     *
     * @return $this
     */
    protected function argForNextCommand($arg)
    {
        return $this->argsForNextCommand($arg);
    }

    /**
     * Add multiple arguments used in the next invocation of drush.
     *
     * @param array|string $args can also be multiple parameters
     *
     * @return $this
     */
    protected function argsForNextCommand($args)
    {
        if (!is_array($args)) {
            $args = func_get_args();
        }
        $this->argumentsForNextCommand .= ' ' . implode(' ', $args);

        return $this;
    }

    public function siteMail($siteMail)
    {
        $this->argForNextCommand('--site-mail=' . $siteMail);

        return $this;
    }

    public function sitesSubdir($sitesSubdir)
    {
        $this->argForNextCommand('--sites-subdir=' . $sitesSubdir);

        return $this;
    }

    public function locale($locale)
    {
        $this->argForNextCommand('--locale=' . $locale);

        return $this;
    }

    public function accountMail($accountMail)
    {
        $this->argForNextCommand('--account-mail=' . $accountMail);

        return $this;
    }

    public function accountName($accountName)
    {
        $this->argForNextCommand('--account-name=' . escapeshellarg($accountName));

        return $this;
    }

    public function accountPass($accountPass)
    {
        $this->argForNextCommand('--account-pass=' . escapeshellarg($accountPass));

        return $this;
    }

    public function dbPrefix($dbPrefix)
    {
        $this->argForNextCommand('--db-prefix=' . $dbPrefix);

        return $this;
    }

    public function dbSu($dbSu)
    {
        $this->argForNextCommand('--db-su=' . $dbSu);

        return $this;
    }

    public function dbSuPw($dbSuPw)
    {
        $this->argForNextCommand('--db-su-pw=' . escapeshellarg($dbSuPw));

        return $this;
    }

    public function sqliteDbUrl($relativePath)
    {
        return $this->dbUrl('sqlite://' . $relativePath);
    }

    public function dbUrl($dbUrl)
    {
        $this->argForNextCommand('--db-url=' . escapeshellarg($dbUrl));

        return $this;
    }

    public function mysqlDbUrl($dsn)
    {
        return $this->dbUrl('mysql://' . $dsn);
    }

    public function disableUpdateStatusModule()
    {
        $this->argForNextCommand('install_configure_form.update_status_module=0');

        return $this;
    }

    public function configDir($configDir)
    {
        $this->argForNextCommand('--config-dir=' . $configDir);

        return $this;
    }

    public function existingConfig($existingConfig = true)
    {
        if ($existingConfig) {
            $this->argForNextCommand('--existing-config');
        }

        return $this;
    }

    /**
     * Executes `drush status`
     *
     * @return $this
     */
    public function status()
    {
        return $this->drush('status');
    }

    /**
     * Runs the given drush command.
     *
     * @param string $command
     * @param bool $assumeYes
     *
     * @return $this
     */
    public function drush($command, $assumeYes = true)
    {
        if (is_array($command)) {
            $command = implode(' ', array_filter($command));
        }

        return $this->exec($this->injectArguments($command, $assumeYes));
    }

    /**
     * Prepends site-alias and appends arguments to the command.
     *
     * @param string $command
     * @param bool $assumeYes
     *
     * @return string the modified command string
     */
    protected function injectArguments($command, $assumeYes)
    {
        $cmd =
            $this->siteAlias . ' '
            . $command
            . ($assumeYes ? ' -y': '')
            . $this->arguments
            . $this->argumentsForNextCommand;
        $this->argumentsForNextCommand = '';

        return $cmd;
    }

    /**
     * Runs pending database updates.
     *
     * @return $this
     */
    public function updateDb()
    {
        $this->printTaskInfo('Do database updates');
        $this->drush('updb');
        $drushVersion = $this->getVersion();
        if (-1 === version_compare($drushVersion, '6.0')) {
            $this->printTaskInfo('Will clear cache after db updates for drush '
                . $drushVersion);
            $this->clearCache();
        } else {
            $this->printTaskInfo('Will not clear cache after db updates, since drush '
                . $drushVersion . ' should do it automatically');
        }

        return $this;
    }

    /**
     * Returns the drush version.
     *
     * @return string
     */
    public function getVersion()
    {
        if (empty($this->drushVersion)) {
            $isPrinted = $this->isPrinted;
            $this->isPrinted = false;
            $result = $this->executeCommand($this->executable . ' version');
            $output = $result->getMessage();
            $this->drushVersion = 'unknown';
            if (preg_match('#[0-9.]+#', $output, $matches)) {
                $this->drushVersion = $matches[0];
            }
            $this->isPrinted = $isPrinted;
        }

        return $this->drushVersion;
    }

    /**
     * Clears the given cache.
     *
     * @param string $name cache name
     *
     * @return $this
     */
    public function clearCache($name = 'all')
    {
        $this->printTaskInfo('Clear cache');

        return $this->drush('cc ' . $name);
    }

    /**
     * @param bool $force force revert even if Features assumes components' state are default
     * @param string $excludedFeatures space-delimited list of features to exclude from being reverted
     *
     * @return $this
     */
    public function revertAllFeatures($force = false, $excludedFeatures = '')
    {
        $this->printTaskInfo('Revert all features');
        $args = $excludedFeatures . ($force ? ' --force' : '');

        return $this->drush('fra ' . $args);
    }

    /**
     * Enables the maintenance mode.
     *
     * @return $this
     */
    public function maintenanceOn()
    {
        $this->printTaskInfo('Turn maintenance mode on');

        return $this->drush('vset --exact maintenance_mode 1');
    }

    /**
     * Disables the maintenance mode.
     *
     * @return $this
     */
    public function maintenanceOff()
    {
        $this->printTaskInfo('Turn maintenance mode off');

        return $this->drush('vdel --exact maintenance_mode');
    }

    /**
     * @param string $installationProfile
     *
     * @return $this
     */
    public function siteInstall($installationProfile)
    {
        return $this->drush('site-install ' . $installationProfile);
    }
}


<?php namespace Williamson\Laragram;

abstract class AbstractWrapperCommands
{

    /**
     * The file handler for the socket-connection
     *
     * @var ressource
     */
    protected $_fp;

    /**
     * Connects to the telegram-cli.
     *
     * @param string $remoteSocket Address of the socket to connect to. See stream_socket_client() for more info.
     *                             Can be 'unix://' or 'tcp://'.
     *
     * @throws ClientException Throws an exception if no connection can be established.
     */
    public function __construct($remoteSocket)
    {
        $this->_fp = @stream_socket_client($remoteSocket);
        if ($this->_fp === false) {
            throw new ClientException('Could not connect to socket "' . $remoteSocket . '"');
        }
        stream_set_timeout($this->_fp, 1); //This way fgets() returns false if telegram-cli gives us no response.
    }

    /**
     * Closes the connection to the telegram-cli.
     */
    public function __destruct()
    {
        if ($this->_fp) {
            fclose($this->_fp);
        }
    }

    /**
     * Executes a command on the telegram-cli. Line-breaks will be escaped, as telgram-cli does not support them.
     *
     * @param string $command The command, including all arguments
     *
     * @return boolean|string Returns the answer as string or true on success, false if there was an error.
     */
    public function exec($command)
    {
        fwrite($this->_fp, str_replace("\n", '\n', $command) . PHP_EOL);

        $answer = fgets($this->_fp); //"ANSWER $bytes" if there is a return value or \n if not
        if (is_string($answer)) {
            if (substr($answer, 0, 7) === 'ANSWER ') {
                $bytes = (int) substr($answer, 7);
                if ($bytes > 0) {
                    $jsonObj = json_decode(trim(fread($this->_fp, $bytes + 1)));

                    if (is_null($jsonObj)) {
                        return 'You must enable the json flag on the telegram daemon to get proper response messages here.';
                    }

                    return $jsonObj;
                }
            } else {
                return $answer;
            }
        }

        return false;
    }

    /**
     * Escapes strings for usage as command-argument.
     * T"es't -> "T\"es\'t"
     *
     * @param string $argument The argument to escape
     *
     * @return string The escaped command enclosed by double-quotes
     */
    public function escapeStringArgument($argument)
    {
        return '"' . addslashes($argument) . '"';
    }

    /**
     * Replaces all spaces with underscores.
     *
     * @param string $peer The peer to escape
     *
     * @return string The escaped peer
     */
    public function escapePeer($peer)
    {
        return str_replace(' ', '_', $peer);
    }

}


<?php
/**
 * Library for urls manipulation.
 *
 * @author    Josantonius - hello@josantonius.com
 * @author    David Carr  - dave@simplemvcframework.com
 * @copyright 2017 - 2018 (c) Josantonius - PHP-Url
 * @license   https://opensource.org/licenses/MIT - The MIT License (MIT)
 * @link      https://github.com/Josantonius/PHP-Url
 * @since     1.0.0
 */
namespace Josantonius\Url;

/**
 * URL handler.
 */
class Url
{
    /**
     * Get URL from the current page.
     *
     * @return string → URL
     */
    public static function getCurrentPage()
    {
        $protocol = self::getProtocol();
        $host = self::getDomain();
        $port = ':' . self::getPort();
        $port = (($port == ':80') || ($port == ':443')) ? '' : $port;
        $uri = self::getUri();

        return $protocol . '://' . $host . $port . $uri;
    }

    /**
     * Get base URL of the site.
     *
     * @since 1.1.0
     *
     * @return string → URL
     */
    public static function getBaseUrl()
    {
        $uri = self::addBackSlash(self::getUriMethods(), 'both');
        $url = self::addBackSlash(self::getCurrentPage());

        if ($uri !== '/') {
            $url = trim(str_replace($uri, '', $url), '/');
        }

        return self::addBackSlash($url);
    }

    /**
     * Get protocol from current or passed URL.
     *
     * @param string $url
     *
     * @return string → http|https
     */
    public static function getProtocol($url = false)
    {
        if ($url) {
            return (preg_match('/^https/', $url)) ? 'https' : 'http';
        }

        $protocol = strtolower($_SERVER['SERVER_PROTOCOL']);
        $protocol = substr($protocol, 0, strpos($protocol, '/'));

        $ssl = (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] == 'on');

        return ($ssl) ? $protocol . 's' : $protocol;
    }

    /**
     * Check if it is a secure site (SSL).
     *
     * @param string $url
     *
     * @return bool
     */
    public static function isSSL($url = false)
    {
        return self::getProtocol($url) === 'https';
    }

    /**
     * Get the server name.
     *
     * @param string $url
     *
     * @return string|false → server name
     */
    public static function getDomain($url = false)
    {
        if ($url) {
            preg_match('/([\w]+[.]){1,}[a-z]+/', $url, $matches);

            return isset($matches[0]) ? $matches[0] : false;
        }

        return $_SERVER['SERVER_NAME'];
    }

    /**
     * Get URI.
     *
     * @return string → path/URL
     */
    public static function getUri()
    {
        return parse_url($_SERVER['REQUEST_URI'], PHP_URL_PATH);
    }

    /**
     * Remove subdirectories from URI if they exist.
     *
     * @return string → method1/method2/method3
     */
    public static function getUriMethods()
    {
        $root = str_replace($_SERVER['DOCUMENT_ROOT'], '', getcwd());
        $subfolder = trim($root, '/');

        return trim(str_replace($subfolder, '', self::getUri()), '/');
    }

    /**
     * Set parameters from the URL and return URL without them.
     *
     * If a URL is received as: http://www.web.com/&key=value&key-2=value
     * params will be saved as GET values and return: http://www.web.com/
     *
     * If a URL is received as: http://www.web.com/?key=value&key-2=value
     * GET parameters are maintained and return: http://www.web.com/
     *
     * @since 1.1.5
     *
     * @param string $url → URL
     *
     * @return string → URL
     */
    public static function setUrlParams($url = false)
    {
        $url = $url !== false ? $url : self::getCurrentPage();

        if (strpos($url, '?') == false && strpos($url, '&') != false) {
            $url = preg_replace('/&/', '?', $url, 1);
            $parts = parse_url($url);
            $query = isset($parts['query']) ? $parts['query'] : '';

            parse_str($query, $query);
        }

        foreach (isset($query) ? $query : [] as $key => $value) {
            $_GET[$key] = $value;
        }

        return explode('?', $url)[0];
    }

    /**
     * Get the server port.
     *
     * @return int → server port
     */
    public static function getPort()
    {
        return $_SERVER['SERVER_PORT'];
    }

    /**
     * Add backslash if it does not exist at the end of the route.
     *
     * @param string $uri      → URI
     * @param string $position → place where the backslash is placed
     *
     * @return string|false → path/url/ | /path/url | /path/url/
     */
    public static function addBackSlash($uri, $position = 'end')
    {
        switch ($position) {
            case 'top':
                $uri = '/' . ltrim($uri, '/');
                break;
            case 'end':
                $uri = rtrim($uri, '/') . '/';
                break;
            case 'both':
                $uri = ! empty($uri) ? '/' . trim($uri, '/') . '/' : '';
                break;
            default:
                $uri = false;
        }

        return $uri;
    }

    /**
     * Go to the previous URL.
     */
    public static function previous()
    {
        header('Location: ' . $_SERVER['HTTP_REFERER']);
        exit;
    }

    /**
     * Redirect to chosen URL.
     *
     * @param string $url → the URL to redirect
     */
    public static function redirect($url)
    {
        header('Location: ' . $url);
        exit;
    }

    /**
     * Converts plain text URLS into HTML links.
     * Second argument will be used as the URL label $custom.
     *
     * @param string $url    → URL
     * @param string $custom → if provided, this is used for the link label
     *
     * @return string → returns the data with links created around URLS
     */
    public static function autoLink($url, $custom = null)
    {
        $regex = '@(http)?(s)?(://)?(([-\w]+\.)+([^\s]+)+[^,.\s])@';

        if ($custom === null) {
            $replace = '$1$2$3$4';
        } else {
            $replace = '' . $custom . '';
        }

        return preg_replace($regex, $replace, $url);
    }

    /**
     * This function converts and URL segment to an safe one.
     * For example: `test name @132` will be converted to `test-name--123`.
     * It will also return all letters in lowercase
     *
     * @param string $slug → URL slug to clean up
     *
     * @return string → slug
     */
    public static function generateSafeSlug($slug)
    {
        $slug = preg_replace('/[^a-zA-Z0-9]/', '-', $slug);
        $slug = strtolower(trim($slug, '-'));
        $slug = preg_replace('/\-{2,}/', '-', $slug);

        return $slug;
    }

    /**
     * Get all URL parts based on a / seperator.
     *
     * @since 1.1.5
     *
     * @param string $uri → URI to segment
     *
     * @return string → segments
     */
    public static function segmentUri($uri = null)
    {
        $uri = (! is_null($uri)) ? $uri : $_SERVER['REQUEST_URI'];

        return explode('/', trim($uri, '/'));
    }

    /**
     * Get first item segment.
     *
     * @return string → segment
     */
    public static function getFirstSegment($segments)
    {
        $var = is_array($segments) ? $segments : self::segmentUri($segments);

        return array_shift($var);
    }

    /**
     * Get last item segment.
     *
     * @return string → segment
     */
    public static function getLastSegment($segments)
    {
        $var = is_array($segments) ? $segments : self::segmentUri($segments);

        return end($var);
    }
}


<?php

namespace DI\Bridge\Silex\Controller;

use Invoker\CallableResolver;
use Invoker\Exception\NotCallableException;
use Invoker\ParameterResolver\ParameterResolver;
use Invoker\Reflection\CallableReflection;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Controller\ControllerResolverInterface;

/**
 * @author Matthieu Napoli <matthieu@mnapoli.fr>
 */
class ControllerResolver implements ControllerResolverInterface
{
    /**
     * @var CallableResolver
     */
    private $callableResolver;

    /**
     * @var ParameterResolver
     */
    private $parameterResolver;

    /**
     * Constructor.
     *
     * @param CallableResolver  $callableResolver
     * @param ParameterResolver $parameterResolver
     */
    public function __construct(CallableResolver $callableResolver, ParameterResolver $parameterResolver)
    {
        $this->callableResolver = $callableResolver;
        $this->parameterResolver = $parameterResolver;
    }

    /**
     * {@inheritdoc}
     */
    public function getController(Request $request)
    {
        if (! $controller = $request->attributes->get('_controller')) {
            throw new \LogicException(sprintf(
                'Controller for URI "%s" could not be found because the "_controller" parameter is missing.',
                $request->getPathInfo()
            ));
        }

        try {
            return $this->callableResolver->resolve($controller);
        } catch (NotCallableException $e) {
            throw new \InvalidArgumentException(sprintf(
                'Controller for URI "%s" is not callable: %s',
                $request->getPathInfo(),
                $e->getMessage()
            ));
        }
    }

    /**
     * {@inheritdoc}
     */
    public function getArguments(Request $request, $controller)
    {
        $controllerReflection = CallableReflection::create($controller);
        $controllerParameters = $controllerReflection->getParameters();
        $resolvedArguments = [];

        foreach ($controllerParameters as $index => $parameter) {
            if ('request' === $parameter->getName() || ($parameter->getClass() && $parameter->getClass()->isInstance($request))) {
                $resolvedArguments[$index] = $request;

                break;
            }
        }

        $arguments = $this->parameterResolver->getParameters(
            $controllerReflection,
            $request->attributes->all(),
            $resolvedArguments
        );

        ksort($arguments);

        // Check if all parameters are resolved
        $diff = array_diff_key($controllerParameters, $arguments);
        if (0 < count($diff)) {
            /** @var \ReflectionParameter $parameter */
            $parameter = reset($diff);
            throw new \RuntimeException(sprintf(
                'Controller "%s" requires that you provide a value for the "$%s" argument.',
                $controllerReflection->getName(),
                $parameter->getName()
            ));
        }

        return $arguments;
    }
}


<?php namespace Williamson\Laragram\Laravel;

use Log;
use Config;
use Williamson\Laragram\TgCommands;
use Illuminate\Support\ServiceProvider;
use Williamson\Laragram\ClientException;

class LaragramServiceProvider extends ServiceProvider
{

    /**
     * Indicates if loading of the provider is deferred.
     *
     * @var bool
     */
    protected $defer = true;

    /**
     * Bootstrap the application services.
     *
     * @return void
     */
    public function boot()
    {

    }

    /**
     * Register the application services.
     *
     * @return void
     */
    public function register()
    {
        $this->app->singleton('Williamson\Laragram\TgCommands', function () {
            try {
                $tg = new TgCommands(Config::get('services.telegram.socket'));
            } catch (ClientException $e) {
                Log::error($e->getMessage());

                return;
            }

            return $tg;
        });
    }

    /**
     * Get the services provided by the provider.
     *
     * @return array
     */
    public function provides()
    {
        return [TgCommands::class];
    }
}


<?php

namespace DevGroup\EventsSystem\helpers;

use DevGroup\EventsSystem\models\Event;
use DevGroup\EventsSystem\models\EventEventHandler;
use DevGroup\EventsSystem\models\EventGroup;
use DevGroup\EventsSystem\models\EventHandler;
use DevGroup\TagDependencyHelper\NamingHelper;
use Yii;
use yii\caching\TagDependency;
use yii\helpers\Json;

/**
 * Class EventHelper
 * @package DevGroup\EventsSystem\helpers
 */
class EventHelper
{
    /**
     * Gat all active handlers.
     * @return array of handlers
     */
    public static function getActiveHandlersList()
    {
        $cacheKey = 'DevGroup/EventsSystem:activeHandlersList';
        $handlers = Yii::$app->cache->get($cacheKey);
        if ($handlers === false) {
            $eventEventHandlers = EventEventHandler::find()
                ->where(['is_active' => 1])
                ->orderBy(['sort_order' => SORT_ASC])
                ->asArray(true)
                ->all();
            $events = Event::find()
                ->where(['id' => array_column($eventEventHandlers, 'event_id', 'event_id')])
                ->indexBy('id')
                ->asArray(true)
                ->all();
            $eventGroups = EventGroup::find()
                ->where(['id' => array_column($events, 'event_group_id', 'event_group_id')])
                ->indexBy('id')
                ->asArray(true)
                ->all();
            $eventHandlers = EventHandler::find()
                ->where(['id' => array_column($eventEventHandlers, 'event_handler_id', 'event_handler_id')])
                ->indexBy('id')
                ->asArray(true)
                ->all();
            $handlers = [];
            foreach ($eventEventHandlers as $eventEventHandler) {
                if (isset(
                        $eventHandlers[$eventEventHandler['event_handler_id']],
                        $events[$eventEventHandler['event_id']],
                        $eventGroups[$events[$eventEventHandler['event_id']]['event_group_id']]
                    ) === false
                ) {
                    continue;
                }
                try {
                    $data = Json::decode($eventEventHandler['packed_json_params']);
                } catch (\Exception $e) {
                    $data = [];
                }
                $handlers[] = [
                    'class' => $eventGroups[$events[$eventEventHandler['event_id']]['event_group_id']]['owner_class_name'],
                    'name' => $events[$eventEventHandler['event_id']]['execution_point'],
                    'callable' => [
                        $eventHandlers[$eventEventHandler['event_handler_id']]['class_name'],
                        $eventEventHandler['method'],
                    ],
                    'data' => $data,
                ];
            }
            Yii::$app->cache->set(
                $cacheKey,
                $handlers,
                86400,
                new TagDependency(
                    [
                        'tags' => [
                            NamingHelper::getCommonTag(EventGroup::className()),
                            NamingHelper::getCommonTag(Event::className()),
                            NamingHelper::getCommonTag(EventHandler::className()),
                            NamingHelper::getCommonTag(EventEventHandler::className()),
                        ],
                    ]
                )
            );
        }
        return $handlers;
    }

    /**
     * Translates a message to the specified language.
     *
     * This is a shortcut method of [[\yii\i18n\I18N::translate()]].
     *
     * The translation will be conducted according to the message category and the target language will be used.
     *
     * You can add parameters to a translation message that will be substituted with the corresponding value after
     * translation. The format for this is to use curly brackets around the parameter name as you can see in the following example:
     *
     * ```php
     * $username = 'Alexander';
     * echo \Yii::t('app', 'Hello, {username}!', ['username' => $username]);
     * ```
     *
     * Further formatting of message parameters is supported using the [PHP intl extensions](http://www.php.net/manual/en/intro.intl.php)
     * message formatter. See [[\yii\i18n\I18N::translate()]] for more details.
     *
     * @param string $message the message to be translated.
     * @param array $params the parameters that will be used to replace the corresponding placeholders in the message.
     * @param string $language the language code (e.g. `en-US`, `en`). If this is null, the current
     * [[\yii\base\Application::language|application language]] will be used.
     * @return string the translated message.
     */
    public static function t($message, $params = [], $language = null)
    {
        return Yii::t('devgroup.events-system', $message, $params, $language);
    }
}


<?php
namespace Logging\Controller\Component;

use Cake\Controller\Component;
use Cake\Controller\ComponentRegistry;
use Cake\Log\Log;

/**
 * Log component
 */
class LogComponent extends Component {

    /**
     * Default configuration.
     *
     * @var array
     */
    protected $_defaultConfig = [
        'request' => false,
        'session' => false,
        'ip'      => false,
        'referer' => false,
        'vars'    => [],
    ];

    /**
     * Writes the given message and type to all of the configured log adapters.
     * Configured adapters are passed both the $level and $message variables. $level
     * is one of the following strings/values.
     *
     * ### Levels:
     *
     * - `LOG_EMERG` => 'emergency',
     * - `LOG_ALERT` => 'alert',
     * - `LOG_CRIT` => 'critical',
     * - `LOG_ERR` => 'error',
     * - `LOG_WARNING` => 'warning',
     * - `LOG_NOTICE` => 'notice',
     * - `LOG_INFO` => 'info',
     * - `LOG_DEBUG` => 'debug',
     *
     * ### Basic usage
     *
     * Write a 'warning' message to the logs:
     *
     * ```
     * Log::write('warning', 'Stuff is broken here');
     * ```
     *
     * ### Using scopes
     *
     * When writing a log message you can define one or many scopes for the message.
     * This allows you to handle messages differently based on application section/feature.
     *
     * ```
     * Log::write('warning', 'Payment failed', ['scope' => 'payment']);
     * ```
     *
     * When configuring loggers you can configure the scopes a particular logger will handle.
     * When using scopes, you must ensure that the level of the message, and the scope of the message
     * intersect with the defined levels & scopes for a logger.
     *
     * ### Unhandled log messages
     *
     * If no configured logger can handle a log message (because of level or scope restrictions)
     * then the logged message will be ignored and silently dropped. You can check if this has happened
     * by inspecting the return of write(). If false the message was not handled.
     *
     * @param int|string $level The severity level of the message being written.
     *    The value must be an integer or string matching a known level.
     * @param mixed $message Message content to log
     * @param string|array $scope key can be passed to be used for further filtering
     * @param array $context Additional data to be used for logging the message.
     *  See Cake\Log\Log::config() for more information on logging scopes.
     * @param  mixed $config : change base config (ex request, session...)
     * @return bool Success
     */
    public function write($level, $scope, $message, $context = [], $config = []) {

        $_context = [];

        $config['request'] = ( isset($config['request']) ) ? $config['request'] : $this->config('request');
        $config['session'] = ( isset($config['session']) ) ? $config['session'] : $this->config('session');
        $config['ip']      = ( isset($config['ip']) ) ? $config['ip'] : $this->config('ip');
        $config['referer'] = ( isset($config['referer']) ) ? $config['referer'] : $this->config('referer');

        if ( $config['request'] )
            $_context['request'] = $this->request;

        if ( $config['session'] )
            $_context['session'] = $this->request->session()->read();

        if ( $config['ip'] )
            $_context['ip'] = $this->request->clientIp();

        if ( $config['referer'] )
            $_context['referer'] = $this->request->referer();

        if ( is_array($this->config('vars')) ) {
            foreach ( $this->config('vars') as $k => $v )
                $_context[$k] = $v;
        }

        $context = array_merge($_context, $context);
        $context['scope'] = (array) $scope;

        return Log::write($level, $message, $context);
    }

    /**
     * Convenience method to log emergency messages
     *
     * @param string|array $scope key can be passed to be used for further filtering
     * @param string $message log message
     * @param array $context Additional data to be used for logging the message.
     *  See Cake\Log\Log::config() for more information on logging scopes.
     * @param  mixed $config : change base config (ex request, session...)
     * @return bool Success
     */
    public function emergency($scope, $message, $context = [], $config = []) {
        return $this->write('emergency', $scope, $message, $context, $config);
    }

    /**
     * Convenience method to log alert messages
     *
     * @param string|array $scope key can be passed to be used for further filtering
     * @param string $message log message
     * @param array $context Additional data to be used for logging the message.
     *  See Cake\Log\Log::config() for more information on logging scopes.
     * @param  mixed $config : change base config (ex request, session...)
     * @return bool Success
     */
    public function alert($scope, $message, $context = [], $config = []) {
        return $this->write('alert', $scope, $message, $context, $config);
    }

    /**
     * Convenience method to log critical messages
     *
     * @param string|array $scope key can be passed to be used for further filtering
     * @param string $message log message
     * @param array $context Additional data to be used for logging the message.
     *  See Cake\Log\Log::config() for more information on logging scopes.
     * @param  mixed $config : change base config (ex request, session...)
     * @return bool Success
     */
    public function critical($scope, $message, $context = [], $config = []) {
        return $this->write('critical', $scope, $message, $context, $config);
    }

    /**
     * Convenience method to log error messages
     *
     * @param string|array $scope key can be passed to be used for further filtering
     * @param string $message log message
     * @param array $context Additional data to be used for logging the message.
     *  See Cake\Log\Log::config() for more information on logging scopes.
     * @param  mixed $config : change base config (ex request, session...)
     * @return bool Success
     */
    public function error($scope, $message, $context = [], $config = []) {
        return $this->write('error', $scope, $message, $context, $config);
    }

    /**
     * Convenience method to log warning messages
     *
     * @param string|array $scope key can be passed to be used for further filtering
     * @param string $message log message
     * @param array $context Additional data to be used for logging the message.
     *  See Cake\Log\Log::config() for more information on logging scopes.
     * @param  mixed $config : change base config (ex request, session...)
     * @return bool Success
     */
    public function warning($scope, $message, $context = [], $config = []) {
        return $this->write('warning', $scope, $message, $context, $config);
    }

    /**
     * Convenience method to log notice messages
     *
     * @param string|array $scope key can be passed to be used for further filtering
     * @param string $message log message
     * @param array $context Additional data to be used for logging the message.
     *  See Cake\Log\Log::config() for more information on logging scopes.
     * @param  mixed $config : change base config (ex request, session...)
     * @return bool Success
     */
    public function notice($scope, $message, $context = [], $config = []) {
        return $this->write('notice', $scope, $message, $context, $config);
    }

    /**
     * Convenience method to log debug messages
     *
     * @param string|array $scope key can be passed to be used for further filtering
     * @param string $message log message
     * @param array $context Additional data to be used for logging the message.
     *  See Cake\Log\Log::config() for more information on logging scopes.
     * @param  mixed $config : change base config (ex request, session...)
     * @return bool Success
     */
    public function debug($scope, $message, $context = [], $config = []) {
        return $this->write('debug', $scope, $message, $context, $config);
    }

    /**
     * Convenience method to log info messages
     *
     * @param string|array $scope key can be passed to be used for further filtering
     * @param string $message log message
     * @param array $context Additional data to be used for logging the message.
     *  See Cake\Log\Log::config() for more information on logging scopes.
     * @param  mixed $config : change base config (ex request, session...)
     * @return bool Success
     */
    public function info($scope, $message, $context = [], $config = []) {
        return $this->write('info', $scope, $message, $context, $config);
    }
}


<?php

namespace DevGroup\EventsSystem\actions;

use yii\base\Action;

/**
 * Class DeleteAction
 * @property \DevGroup\EventsSystem\controllers\HandlersManageController $controller
 * @package DevGroup\EventsSystem\actions
 */
class DeleteAction extends Action
{
    /**
     * Deletes an existing EventEventHandler model.
     * If deletion is successful, the browser will be redirected to the 'index' page.
     * @param integer $id
     * @return mixed
     */
    public function run($id)
    {
        $model = $this->controller->findModel($id);
        if ($model->is_system) {
            \Yii::$app->session->setFlash('warning', \Yii::t('app', 'You cannot update or delete system handlers'));
        } else {
            $model->delete();
        }
        return $this->controller->redirect(['index']);
    }
}


<?php
namespace Logging\Log\Engine;
use Cake\Log\Engine\BaseLog;
use Cake\ORM\TableRegistry;
use Cake\Utility\Hash;

class DatabaseLog extends BaseLog {

    /**
     * Default config for this class
     *
     * @var array
     */
    protected $_defaultConfig = [
        'levels'        => [],
        'scopes'        => [],
        'requiredScope' => false,
        'model'         => 'Logging.Logs',
        'table'         => 'logs',
        'userId'        => 'Auth.User.id',
    ];

    protected $_context = [];

    public function __construct($config = []) {
        parent::__construct($config);
    }

    /**
     * Implements writing to log files.
     *
     * @param string $level The severity level of the message being written.
     *    See Cake\Log\Log::$_levels for list of possible levels.
     * @param string $message The message you want to log.
     * @param array $context Additional information about the logged message
     * @return bool success of write.
     */
    public function log($level, $message, array $context = []) {

        if ( $this->config('requiredScope') && ( empty($context['scope']) ) )
            return false;

        $scopes = ( empty($context['scope']) ) ? [null] : $context['scope'];
        unset($context['scope']);
        $this->_context = $context;

        $Table = TableRegistry::get($this->config('model'), ['table' => $this->config('table')]);

        foreach ( $scopes as $scope ) {
            $entity = $Table->newEntity();

            $data = [
                'level'   => $level,
                'user_id' => $this->_userId(),
                'scope'   => $scope,
                'message' => $message,
                'context' => $this->_context,
            ];

            $entity = $Table->patchEntity($entity, $data);

            $Table->save($entity);
        }

        return true;
    }

    /**
     * Get user_id
     * @return int
     */
    protected function _userId() {
        if ( array_key_exists('userId', $this->_context) ) {
            $userId = ($this->_context['userId']) ? (int) $this->_context['userId'] : null;
            unset($this->_context['userId']);
            return $userId;
        }

        if ( isset($_SESSION) && is_array($_SESSION) )
            return Hash::get($_SESSION, $this->config('userId'));

        return null;
    }
}


<?php

namespace DevGroup\EventsSystem;

use DevGroup\EventsSystem\helpers\EventHelper;
use yii\base\Application;
use yii\base\Event;
use yii\helpers\Html;

/**
 * Class Bootstrap
 * @package DevGroup\EventsSystem
 */
class Bootstrap implements \yii\base\BootstrapInterface
{
    /**
     * Bootstrap method to be called during application bootstrap stage.
     * @param \yii\base\Application $app the application currently running
     */
    public function bootstrap($app)
    {
        $app->i18n->translations['devgroup.events-system'] = [
            'class' => 'yii\i18n\PhpMessageSource',
            'sourceLanguage' => 'en-US',
            'basePath' => '@DevGroup/EventsSystem/messages',
        ];
        $error = [];
        try {
            foreach (EventHelper::getActiveHandlersList() as $handler) {
                Event::on($handler['class'], $handler['name'], $handler['callable'], $handler['data']);
            }
        } catch (\yii\db\Exception $e) {
            $error = [
                'message' => '`DevGroup\EventsSystem` extension is not fully installed yet.',
                'hint' => 'Please run the `./yii migrate --migrationPath=@DevGroup/EventsSystem/migrations` command from your application directory to finish the installation process.',
            ];
        } catch (\Exception $e) {
            $error = [
                'message' => $e->getCode(),
                'hint' => $e->getMessage(),
            ];
        }
        if (empty($error) === false) {
            if ($app instanceof \yii\console\Application) {
                $app->on(Application::EVENT_BEFORE_ACTION, function ($event) use ($app, $error) {
                    $app->controller->stdout(PHP_EOL . str_repeat('=', 80) . PHP_EOL . PHP_EOL);
                    $app->controller->stderr($error['message'] . PHP_EOL);
                    $app->controller->stdout($error['hint'] . PHP_EOL);
                    $app->controller->stdout(PHP_EOL . str_repeat('=', 80) . PHP_EOL);
                });
            } elseif ($app instanceof \yii\web\Application && YII_DEBUG === true) {
                $app->session->setFlash(
                    'warning',
                    Html::tag('h4', $error['message']) . Html::tag('p', $error['hint'])
                );
            }
        }
    }
}


<?php

namespace DevGroup\EventsSystem\actions;

use DevGroup\EventsSystem\models\Event;
use DevGroup\EventsSystem\models\EventEventHandler;
use DevGroup\EventsSystem\models\EventGroup;
use DevGroup\EventsSystem\models\EventHandler;
use yii\base\Action;
use yii\base\Exception;

/**
 * Class ListAction
 * @property \DevGroup\EventsSystem\controllers\ManageController $controller
 * @package DevGroup\EventsSystem\actions
 */
class ListAction extends Action
{
    /**
     * Lists all EventEventHandler models.
     * @return mixed
     */
    public function run($eventGroupId = null)
    {
        $eventGroups = EventGroup::find()->asArray(true)->all();
        if (count($eventGroups) === 0) {
            throw new Exception('Event groups not found');
        }
        if ($eventGroupId === null) {
            $first = reset($eventGroups);
            $eventGroupId = $first['id'];
        }
        $tabs = [];
        foreach ($eventGroups as $eventGroup) {
            $tabs[] = [
                'label' => $eventGroup['name'],
                'url' => ['index', 'eventGroupId' => $eventGroup['id']],
                'active' => $eventGroupId == $eventGroup['id'],
            ];
        }
        $model = new EventEventHandler(['scenario' => 'search']);
        $eventsList = Event::find()
            ->select(['name', 'id'])
            ->where(['event_group_id' => $eventGroupId])
            ->indexBy('id')
            ->column();
        return $this->controller->render(
            'index',
            [
                'dataProvider' => $model->search(\Yii::$app->request->get(), array_keys($eventsList)),
                'eventGroupId' => $eventGroupId,
                'eventsList' => $eventsList,
                'model' => $model,
                'tabs' => $tabs,
            ]
        );
    }
}


<?php

namespace DevGroup\EventsSystem\traits;

use yii\db\ActiveRecord;
use yii\helpers\ArrayHelper;

/**
 * Class ListData
 *
 * @mixin ActiveRecord
 * @package DevGroup\EventsSystem\traits
 */
trait ListData
{
    /**
     * @var bool whether is the list already loaded
     */
    protected static $listIsFilled = false;

    /**
     * @param string|callable $attributeName
     * @return array
     */
    public static function dropDownList($attributeName = 'name')
    {
        $list = ArrayHelper::map(
            self::$listIsFilled === true ? static::getMap() : static::find()->asArray(true)->all(),
            'id',
            $attributeName
        );
        self::$listIsFilled = true;
        return $list;
    }

    /**
     * Get the record name by id.
     * @param integer $id
     * @param string $attributeName
     * @return string
     */
    public static function getNameById($id, $attributeName = 'name')
    {
        $model = static::loadModel($id);
        return empty($model[$attributeName]) === false ? $model[$attributeName] : \Yii::t('app', 'Unknown');
    }

    /**
     * Preload data.
     * This method loads all models to identity map as array of attributes
     */
    public static function preloadData()
    {
        static::find()->asArray(true)->all();
        self::$listIsFilled = true;
    }
}


<?php

/**
 * @link https://github.com/CatoTH/html2opendocument
 * @author Tobias Hößl <tobias@hoessl.eu>
 * @license https://opensource.org/licenses/MIT
 */

namespace CatoTH\HTML2OpenDocument;

class Text extends Base
{
    /** @var null|\DOMElement */
    private $nodeText = null;

    /** @var bool */
    private $node_template_1_used = false;

    /** @var string[] */
    private $replaces = [];

    /** @var array */
    private $textBlocks = [];

    const STYLE_INS = 'ins';
    const STYLE_DEL = 'del';

    /**
     * @param array $options
     *
     * @throws \Exception
     */
    public function __construct($options = [])
    {
        if (isset($options['templateFile']) && $options['templateFile'] != '') {
            $templateFile = $options['templateFile'];
        } else {
            $templateFile = __DIR__ . DIRECTORY_SEPARATOR . 'default-template.odt';
        }
        parent::__construct($templateFile, $options);
    }

    /**
     * @param string $filename
     */
    public function finishAndOutputOdt($filename = '')
    {
        header('Content-Type: application/vnd.oasis.opendocument.text');
        if ($filename != '') {
            header('Content-disposition: attachment;filename="' . addslashes($filename) . '"');
        }

        echo $this->finishAndGetDocument();

        die();
    }

    /**
     * @param string $search
     * @param string $replace
     */
    public function addReplace($search, $replace)
    {
        $this->replaces[$search] = $replace;
    }

    /**
     * @param string $html
     * @param bool $lineNumbered
     */
    public function addHtmlTextBlock($html, $lineNumbered = false)
    {
        $this->textBlocks[] = ['text' => $html, 'lineNumbered' => $lineNumbered];
    }

    /**
     * @param \DOMElement $element
     * @return string[]
     */
    protected static function getCSSClasses(\DOMElement $element)
    {
        if ($element->hasAttribute('class')) {
            return explode(' ', $element->getAttribute('class'));
        } else {
            return [];
        }
    }

    /**
     * @param \DOMElement $element
     * @param string[] $parentStyles
     * @return string[]
     */
    protected static function getChildStyles(\DOMElement $element, $parentStyles = [])
    {
        $classes     = static::getCSSClasses($element);
        $childStyles = $parentStyles;
        if (in_array('ins', $classes)) {
            $childStyles[] = static::STYLE_INS;
        }
        if (in_array('inserted', $classes)) {
            $childStyles[] = static::STYLE_INS;
        }
        if (in_array('del', $classes)) {
            $childStyles[] = static::STYLE_DEL;
        }
        if (in_array('deleted', $classes)) {
            $childStyles[] = static::STYLE_DEL;
        }
        return array_unique($childStyles);
    }

	/**
	 * @param string[] $classes
	 *
	 * @return null|string
	 */
    protected static function cssClassesToInternalClass($classes)
    {
        if (in_array('underline', $classes)) {
            return 'AntragsgruenUnderlined';
        }
        if (in_array('strike', $classes)) {
            return 'AntragsgruenStrike';
        }
        if (in_array('ins', $classes)) {
            return 'AntragsgruenIns';
        }
        if (in_array('inserted', $classes)) {
            return 'AntragsgruenIns';
        }
        if (in_array('del', $classes)) {
            return 'AntragsgruenDel';
        }
        if (in_array('deleted', $classes)) {
            return 'AntragsgruenDel';
        }
        if (in_array('superscript', $classes)) {
            return 'AntragsgruenSup';
        }
        if (in_array('subscript', $classes)) {
            return 'AntragsgruenSub';
        }
        return null;
    }

    /**
     * Wraps all child nodes with text:p nodes, if necessary
     * (it's not necessary for child nodes that are p's themselves or lists)
     *
     * @param \DOMElement $parentEl
     * @param boolean $lineNumbered
     *
     * @return \DOMElement
     */
    protected function wrapChildrenWithP(\DOMElement $parentEl, $lineNumbered)
    {
        $childNodes = [];
        while ($parentEl->childNodes->length > 0) {
            $el = $parentEl->firstChild;
            $parentEl->removeChild($el);
            $childNodes[] = $el;
        }

        $appendNode = null;
        foreach ($childNodes as $childNode) {
            if (in_array(strtolower($childNode->nodeName), ['p', 'list'])) {
                if ($appendNode) {
                    $parentEl->appendChild($appendNode);
                    $appendNode = null;
                }
                $parentEl->appendChild($childNode);
            } else {
                if (!$appendNode) {
                    $appendNode = $this->getNextNodeTemplate($lineNumbered);
                }
                $appendNode->appendChild($childNode);
            }
        }
        if ($appendNode) {
            $parentEl->appendChild($appendNode);
        }

        return $parentEl;
    }

    /**
     * @param \DOMNode $srcNode
     * @param bool $lineNumbered
     * @param bool $inP
     * @param string[]   $parentStyles
     *
     * @return \DOMNode[]
     * @throws \Exception
     */
    protected function html2ooNodeInt($srcNode, $lineNumbered, $inP, $parentStyles = [])
    {
        switch ($srcNode->nodeType) {
            case XML_ELEMENT_NODE:
                /** @var \DOMElement $srcNode */
                if ($this->DEBUG) {
                    echo "Element - " . $srcNode->nodeName . " / Children: " . $srcNode->childNodes->length . "
";
                }
                $needsIntermediateP = false;
                $childStyles        = static::getChildStyles($srcNode, $parentStyles);
                switch ($srcNode->nodeName) {
                    case 'b':
                    case 'strong':
                        $dstEl = $this->doc->createElementNS(static::NS_TEXT, 'span');
                        $dstEl->setAttribute('text:style-name', 'AntragsgruenBold');
                        break;
                    case 'i':
                    case 'em':
                        $dstEl = $this->doc->createElementNS(static::NS_TEXT, 'span');
                        $dstEl->setAttribute('text:style-name', 'AntragsgruenItalic');
                        break;
                    case 's':
                        $dstEl = $this->doc->createElementNS(static::NS_TEXT, 'span');
                        $dstEl->setAttribute('text:style-name', 'AntragsgruenStrike');
                        break;
                    case 'u':
                        $dstEl = $this->doc->createElementNS(static::NS_TEXT, 'span');
                        $dstEl->setAttribute('text:style-name', 'AntragsgruenUnderlined');
                        break;
                    case 'sub':
                        $dstEl = $this->doc->createElementNS(static::NS_TEXT, 'span');
                        $dstEl->setAttribute('text:style-name', 'AntragsgruenSub');
                        break;
                    case 'sup':
                        $dstEl = $this->doc->createElementNS(static::NS_TEXT, 'span');
                        $dstEl->setAttribute('text:style-name', 'AntragsgruenSup');
                        break;
                    case 'br':
                        $dstEl = $this->doc->createElementNS(static::NS_TEXT, 'line-break');
                        break;
                    case 'del':
                        $dstEl = $this->doc->createElementNS(static::NS_TEXT, 'span');
                        $dstEl->setAttribute('text:style-name', 'AntragsgruenDel');
                        break;
                    case 'ins':
                        $dstEl = $this->doc->createElementNS(static::NS_TEXT, 'span');
                        $dstEl->setAttribute('text:style-name', 'AntragsgruenIns');
                        break;
                    case 'a':
                        $dstEl = $this->doc->createElementNS(static::NS_TEXT, 'a');
                        try {
                            $attr = $srcNode->getAttribute('href');
                            if ($attr) {
                                $dstEl->setAttribute('xlink:href', $attr);
                            }
                        } catch (\Exception $e) {
                        }
                        break;
                    case 'p':
                        if ($inP) {
                            $dstEl = $this->createNodeWithBaseStyle('span', $lineNumbered);
                        } else {
                            $dstEl = $this->createNodeWithBaseStyle('p', $lineNumbered);
                        }
                        $intClass = static::cssClassesToInternalClass(static::getCSSClasses($srcNode));
                        if ($intClass) {
                            $dstEl->setAttribute('text:style-name', $intClass);
                        }
                        $inP = true;
                        break;
                    case 'div':
                        // We're basically ignoring DIVs here, as there is no corresponding element in OpenDocument
                        // Therefore no support for styles and classes set on DIVs yet.
                        $dstEl = null;
                        break;
                    case 'blockquote':
                        $dstEl = $this->createNodeWithBaseStyle('p', $lineNumbered);
                        $class = ($lineNumbered ? 'Blockquote_Linenumbered' : 'Blockquote');
                        $dstEl->setAttribute('text:style-name', 'Antragsgrün_20_' . $class);
                        if ($srcNode->childNodes->length == 1) {
                            foreach ($srcNode->childNodes as $child) {
                                if ($child->nodeName == 'p') {
                                    $srcNode = $child;
                                }
                            }
                        }
                        $inP = true;
                        break;
                    case 'ul':
                        $dstEl = $this->doc->createElementNS(static::NS_TEXT, 'list');
                        break;
                    case 'ol':
                        $dstEl = $this->doc->createElementNS(static::NS_TEXT, 'list');
                        break;
                    case 'li':
                        $dstEl              = $this->doc->createElementNS(static::NS_TEXT, 'list-item');
                        $needsIntermediateP = true;
                        $inP                = true;
                        break;
                    case 'h1':
                        $dstEl = $this->createNodeWithBaseStyle('p', $lineNumbered);
                        $dstEl->setAttribute('text:style-name', 'Antragsgrün_20_H1');
                        $inP = true;
                        break;
                    case 'h2':
                        $dstEl = $this->createNodeWithBaseStyle('p', $lineNumbered);
                        $dstEl->setAttribute('text:style-name', 'Antragsgrün_20_H2');
                        $inP = true;
                        break;
                    case 'h3':
                        $dstEl = $this->createNodeWithBaseStyle('p', $lineNumbered);
                        $dstEl->setAttribute('text:style-name', 'Antragsgrün_20_H3');
                        $inP = true;
                        break;
                    case 'h4':
                    case 'h5':
                    case 'h6':
                        $dstEl = $this->createNodeWithBaseStyle('p', $lineNumbered);
                        $dstEl->setAttribute('text:style-name', 'Antragsgrün_20_H4');
                        $inP = true;
                        break;
                    case 'span':
                    default:
                        $dstEl    = $this->doc->createElementNS(static::NS_TEXT, 'span');
                        $intClass = static::cssClassesToInternalClass(static::getCSSClasses($srcNode));
                        if ($intClass) {
                            $dstEl->setAttribute('text:style-name', $intClass);
                        }
                        break;
                }

                if ($dstEl === null) {
                    $ret = [];
                    foreach ($srcNode->childNodes as $child) {
                        /** @var \DOMNode $child */
                        if ($this->DEBUG) {
                            echo "CHILD
" . $child->nodeType . "
";
                        }

                        $dstNodes = $this->html2ooNodeInt($child, $lineNumbered, $inP, $childStyles);
                        foreach ($dstNodes as $dstNode) {
                            $ret[] = $dstNode;
                        }
                    }
                    return $ret;
                }

                foreach ($srcNode->childNodes as $child) {
                    /** @var \DOMNode $child */
                    if ($this->DEBUG) {
                        echo "CHILD
" . $child->nodeType . "
";
                    }

                    $dstNodes = $this->html2ooNodeInt($child, $lineNumbered, $inP, $childStyles);
                    foreach ($dstNodes as $dstNode) {
                        $dstEl->appendChild($dstNode);
                    }
                }

                if ($needsIntermediateP && $dstEl->childNodes->length > 0) {
                    $dstEl = static::wrapChildrenWithP($dstEl, $lineNumbered);
                }
                return [$dstEl];
            case XML_TEXT_NODE:
                /** @var \DOMText $srcNode */
                $textnode       = new \DOMText();
                $textnode->data = $srcNode->data;
                if ($this->DEBUG) {
                    echo 'Text
';
                }
                if (in_array(static::STYLE_DEL, $parentStyles)) {
                    $dstEl = $this->createNodeWithBaseStyle('span', $lineNumbered);
                    $dstEl->setAttribute('text:style-name', 'AntragsgruenDel');
                    $dstEl->appendChild($textnode);
                    $textnode = $dstEl;
                }
                if (in_array(static::STYLE_INS, $parentStyles)) {
                    $dstEl = $this->createNodeWithBaseStyle('span', $lineNumbered);
                    $dstEl->setAttribute('text:style-name', 'AntragsgruenIns');
                    $dstEl->appendChild($textnode);
                    $textnode = $dstEl;
                }
                return [$textnode];
                break;
            case XML_DOCUMENT_TYPE_NODE:
                if ($this->DEBUG) {
                    echo 'Type Node
';
                }
                return [];
            default:
                if ($this->DEBUG) {
                    echo 'Unknown Node: ' . $srcNode->nodeType . '
';
                }
                return [];
        }
    }

	/**
	 * @param string $html
	 * @param bool $lineNumbered
	 *
	 * @return \DOMNode[]
	 * @throws \Exception
	 */
    protected function html2ooNodes($html, $lineNumbered)
    {
        if (!is_string($html)) {
            echo print_r($html, true);
            echo print_r(debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS), true);
            die();
        }

        $body = $this->html2DOM($html);

        $retNodes = [];
        for ($i = 0; $i < $body->childNodes->length; $i++) {
            $child = $body->childNodes->item($i);

            /** @var \DOMNode $child */
            if ($child->nodeName == 'ul') {
                // Alle anderen Nodes dieses Aufrufs werden ignoriert
                if ($this->DEBUG) {
                    echo 'LIST
';
                }
                $recNewNodes = $this->html2ooNodeInt($child, $lineNumbered, false);
            } else {
                if ($child->nodeType == XML_TEXT_NODE) {
                    $new_node = $this->getNextNodeTemplate($lineNumbered);
                    /** @var \DOMText $child */
                    if ($this->DEBUG) {
                        echo $child->nodeName . ' - ' . htmlentities($child->data, ENT_COMPAT, 'UTF-8') . '
';
                    }
                    $text       = new \DOMText();
                    $text->data = $child->data;
                    $new_node->appendChild($text);
                    $recNewNodes = [$new_node];
                } else {
                    if ($this->DEBUG) {
                        echo $child->nodeName . '!!!!!!!!!!!!
';
                    }
                    $recNewNodes = $this->html2ooNodeInt($child, $lineNumbered, false);
                }
            }
            foreach ($recNewNodes as $recNewNode) {
                $retNodes[] = $recNewNode;
            }
        }

        return $retNodes;
    }

	/**
	 * @return string
	 * @throws \Exception
	 */
    public function create()
    {
        $this->appendTextStyleNode('AntragsgruenBold', [
            'fo:font-weight'            => 'bold',
            'style:font-weight-asian'   => 'bold',
            'style:font-weight-complex' => 'bold',
        ]);
        $this->appendTextStyleNode('AntragsgruenItalic', [
            'fo:font-style'            => 'italic',
            'style:font-style-asian'   => 'italic',
            'style:font-style-complex' => 'italic',
        ]);
        $this->appendTextStyleNode('AntragsgruenUnderlined', [
            'style:text-underline-width' => 'auto',
            'style:text-underline-color' => 'font-color',
            'style:text-underline-style' => 'solid',
        ]);
        $this->appendTextStyleNode('AntragsgruenStrike', [
            'style:text-line-through-style' => 'solid',
            'style:text-line-through-type'  => 'single',
        ]);
        $this->appendTextStyleNode('AntragsgruenIns', [
            'fo:color'                   => '#008800',
            'style:text-underline-style' => 'solid',
            'style:text-underline-width' => 'auto',
            'style:text-underline-color' => 'font-color',
            'fo:font-weight'             => 'bold',
            'style:font-weight-asian'    => 'bold',
            'style:font-weight-complex'  => 'bold',
        ]);
        $this->appendTextStyleNode('AntragsgruenDel', [
            'fo:color'                      => '#880000',
            'style:text-line-through-style' => 'solid',
            'style:text-line-through-type'  => 'single',
            'fo:font-style'                 => 'italic',
            'style:font-style-asian'        => 'italic',
            'style:font-style-complex'      => 'italic',
        ]);
        $this->appendTextStyleNode('AntragsgruenSub', [
            'style:text-position' => 'sub 58%',
        ]);
        $this->appendTextStyleNode('AntragsgruenSup', [
            'style:text-position' => 'super 58%',
        ]);

        /** @var \DOMNode[] $nodes */
        $nodes = [];
        foreach ($this->doc->getElementsByTagNameNS(static::NS_TEXT, 'span') as $element) {
            $nodes[] = $element;
        }
        foreach ($this->doc->getElementsByTagNameNS(static::NS_TEXT, 'p') as $element) {
            $nodes[] = $element;
        }

        $searchFor   = array_keys($this->replaces);
        $replaceWith = array_values($this->replaces);
        foreach ($nodes as $node) {
            $children = $node->childNodes;
            foreach ($children as $child) {
                if ($child->nodeType == XML_TEXT_NODE) {
                    /** @var \DOMText $child */
                    $child->data = preg_replace($searchFor, $replaceWith, $child->data);

                    if (preg_match("/\{\{ANTRAGSGRUEN:DUMMY\}\}/siu", $child->data)) {
                        $node->parentNode->removeChild($node);
                    }
                    if (preg_match("/\{\{ANTRAGSGRUEN:TEXT\}\}/siu", $child->data)) {
                        $this->nodeText = $node;
                    }
                }
            }
        }

        foreach ($this->textBlocks as $textBlock) {
            $newNodes = $this->html2ooNodes($textBlock['text'], $textBlock['lineNumbered']);
            foreach ($newNodes as $newNode) {
                $this->nodeText->parentNode->insertBefore($newNode, $this->nodeText);
            }
        }

        $this->nodeText->parentNode->removeChild($this->nodeText);

        return $this->doc->saveXML();
    }

    /**
     * @param bool $lineNumbers
     *
     * @return \DOMNode
     */
    protected function getNextNodeTemplate($lineNumbers)
    {
        $node = $this->nodeText->cloneNode();
        /** @var \DOMElement $node */
        if ($lineNumbers) {
            if ($this->node_template_1_used) {
                $node->setAttribute('text:style-name', 'Antragsgrün_20_LineNumbered_20_Standard');
            } else {
                $this->node_template_1_used = true;
                $node->setAttribute('text:style-name', 'Antragsgrün_20_LineNumbered_20_First');
            }
        } else {
            $node->setAttribute('text:style-name', 'Antragsgrün_20_Standard');
        }

        return $node;
    }

    /**
     * @param string $nodeType
     * @param bool $lineNumbers
     *
     * @return \DOMElement|\DOMNode
     */
    protected function createNodeWithBaseStyle($nodeType, $lineNumbers)
    {
        $node = $this->doc->createElementNS(static::NS_TEXT, $nodeType);
        if ($lineNumbers) {
            if ($this->node_template_1_used) {
                $node->setAttribute('text:style-name', 'Antragsgrün_20_LineNumbered_20_Standard');
            } else {
                $this->node_template_1_used = true;
                $node->setAttribute('text:style-name', 'Antragsgrün_20_LineNumbered_20_First');
            }
        } else {
            $node->setAttribute('text:style-name', 'Antragsgrün_20_Standard');
        }

        return $node;
    }
}