<?php

/*
 * This file is part of the stream package for Icicle, a library for writing asynchronous code in PHP.
 *
 * @copyright 2014-2015 Aaron Piotrowski. All rights reserved.
 * @license MIT See the LICENSE file that was distributed with this source code for more information.
 */

namespace Icicle\Stream;

use Icicle\Exception\InvalidArgumentError;
use Icicle\Stream\Exception\{
    OutOfBoundsException,
    UnreadableException,
    UnseekableException,
    UnwritableException
};

/**
 * Acts as a buffered sink with a seekable read/write pointer. All data written to the sink remains in the sink. The
 * read/write pointer may be moved anywhere within the buffered sink using seek(). The current position of the pointer
 * may be determined with tell(). Since all data remains in the sink, the entire length of the sink is available with
 * getLength().
 */
class MemorySink implements DuplexStream, SeekableStream
{
    /**
     * @var bool
     */
    private $open = true;

    /**
     * @var bool
     */
    private $writable = true;

    /**
     * @var \Icicle\Stream\Structures\Buffer
     */
    private $buffer;

    /**
     * @var \Icicle\Stream\Structures\BufferIterator
     */
    private $iterator;

    /**
     * Initializes a sink with the given data.
     *
     * @param string $data
     */
    public function __construct(string $data = '')
    {
        $this->buffer = new Structures\Buffer($data);
        $this->iterator = $this->buffer->getIterator();
    }

    /**
     * {@inheritdoc}
     */
    public function isOpen(): bool
    {
        return $this->open;
    }

    /**
     * {@inheritdoc}
     */
    public function close()
    {
        $this->open = false;
        $this->writable = false;
    }

    /**
     * {@inheritdoc}
     */
    public function isReadable(): bool
    {
        return $this->isOpen() && $this->iterator->valid();
    }

    /**
     * {@inheritdoc}
     */
    public function read(int $length = 0, string $byte = null, float $timeout = 0): \Generator
    {
        if (!$this->isReadable()) {
            throw new UnreadableException('The stream is no longer readable.');
        }

        if (0 > $length) {
            throw new InvalidArgumentError('The length should be a positive integer.');
        }

        $byte = strlen($byte) ? $byte[0] : null;

        if (null !== $byte) {
            $data = '';
            $i = 0;
            do {
                $char = $this->iterator->current();
                $this->iterator->next();
                $data .= $char;
            } while ($char !== $byte && (0 === $length || ++$i < $length) && $this->iterator->valid());

            return $data;
        }

        if (0 === $length) {
            $length = $this->buffer->getLength();
        }

        $position = $this->iterator->key();
        $data = $this->buffer->peek($length, $position);
        $position = $length + $position;

        if ($position > $this->buffer->getLength()) {
            $position = $this->buffer->getLength();
        }

        $this->iterator->seek($position);

        return $data;

        yield; // Unreachable, but makes the method a coroutine.
    }

    /**
     * {@inheritdoc}
     */
    public function isWritable(): bool
    {
        return $this->writable;
    }

    /**
     * {@inheritdoc}
     */
    public function write(string $data, float $timeout = 0): \Generator
    {
        return $this->send($data, $timeout, false);
    }

    /**
     * {@inheritdoc}
     */
    public function end(string $data = '', float $timeout = 0): \Generator
    {
        return $this->send($data, $timeout, true);
    }

    /**
     * @coroutine
     *
     * @param string $data
     * @param float|int $timeout
     * @param bool $end
     *
     * @return \Generator
     *
     * @throws \Icicle\Stream\Exception\UnwritableException If the stream is no longer writable.
     */
    protected function send(string $data, float $timeout = 0, bool $end = false): \Generator
    {
        if (!$this->isWritable()) {
            throw new UnwritableException('The stream is no longer writable.');
        }

        if ($end) {
            $this->writable = false;
        }

        $length = strlen($data);

        if (!$this->iterator->valid()) {
            $this->buffer->push($data);
        } else {
            $this->iterator->insert($data);
        }

        $this->iterator->seek($this->iterator->key() + $length);

        return $length;

        yield; // Unreachable, but makes the method a coroutine.
    }

    /**
     * {@inheritdoc}
     */
    public function seek(int $offset, int $whence = SEEK_SET, float $timeout = 0): \Generator
    {
        if (!$this->isOpen()) {
            throw new UnseekableException('The stream is no longer seekable.');
        }

        $offset = (int) $offset;

        switch ($whence) {
            case SEEK_SET:
                break;

            case SEEK_CUR:
                $offset += $this->tell();
                break;

            case SEEK_END:
                $offset += $this->getLength();
                break;

            default:
                throw new InvalidArgumentError('Invalid value for whence. Use SEEK_SET, SEEK_CUR, or SEEK_END.');
        }

        if (0 > $offset || $this->buffer->getLength() < $offset) {
            throw new OutOfBoundsException(sprintf('Invalid offset: %s.', $offset));
        }

        $this->iterator->seek($offset);

        return $offset;

        yield; // Unreachable, but makes the method a coroutine.
    }

    /**
     * {@inheritdoc}
     */
    public function tell(): int
    {
        return $this->iterator->key();
    }

    /**
     * {@inheritdoc}
     */
    public function getLength(): int
    {
        return $this->buffer->getLength();
    }
}


<?php

/*
 * This file is part of the stream package for Icicle, a library for writing asynchronous code in PHP.
 *
 * @copyright 2015 Aaron Piotrowski. All rights reserved.
 * @license MIT See the LICENSE file that was distributed with this source code for more information.
 */

namespace Icicle\Stream\Pipe;

use Icicle\Awaitable\{Delayed, Exception\TimeoutException};
use Icicle\Loop;
use Icicle\Loop\Watcher\Io;
use Icicle\Stream\Exception\{ClosedException, FailureException, UnwritableException};
use Icicle\Stream\{StreamResource, WritableStream};
use Throwable;

class WritablePipe extends StreamResource implements WritableStream
{
    /**
     * Queue of data to write and promises to resolve when that data is written (or fails to write).
     * Data is stored as an array: [string, int, int|float|null, Delayed].
     *
     * @var \SplQueue
     */
    private $writeQueue;

    /**
     * @var bool
     */
    private $writable = true;

    /**
     * @var \Icicle\Loop\Watcher\Io|null
     */
    private $await;

    /**
     * @var \Closure
     */
    private $onCancelled;

    /**
     * @param resource $resource Stream resource.
     * @param bool $autoClose True to close the resource on destruct, false to leave it open.
     */
    public function __construct($resource, bool $autoClose = true)
    {
        parent::__construct($resource, $autoClose);

        stream_set_write_buffer($resource, 0);
        stream_set_chunk_size($resource, self::CHUNK_SIZE);

        $this->writeQueue = new \SplQueue();

        $this->onCancelled = function (Throwable $exception) {
            $this->free($exception);
        };
    }

    /**
     * Frees resources associated with this object from the loop.
     */
    public function __destruct()
    {
        parent::__destruct();
        if (null !== $this->await) {
            $this->await->free();
        }
    }

    /**
     * {@inheritdoc}
     */
    public function close()
    {
        parent::close();
        $this->free();
    }

    /**
     * Frees all resources used by the writable stream.
     *
     * @param \Throwable|null $exception
     */
    private function free(Throwable $exception = null)
    {
        $this->writable = false;

        if (null !== $this->await) {
            $this->await->free();
        }

        while (!$this->writeQueue->isEmpty()) {
            /** @var \Icicle\Awaitable\Delayed $delayed */
            list( , , , $delayed) = $this->writeQueue->shift();
            $delayed->reject(
                $exception = $exception ?: new ClosedException('The stream was unexpectedly closed.')
            );
        }
    }

    /**
     * {@inheritdoc}
     */
    public function write(string $data, float $timeout = 0): \Generator
    {
        return $this->send($data, $timeout, false);
    }

    /**
     * Writes the given data to the stream, immediately making the stream unwritable if $end is true.
     *
     * @param string $data
     * @param int $timeout
     * @param bool $end
     *
     * @return \Generator
     *
     * @throws \Icicle\Stream\Exception\UnwritableException If the stream is no longer writable.
     * @throws \Icicle\Stream\Exception\FailureException If writing to the stream fails.
     */
    private function send(string $data, float $timeout = 0, bool $end = false)
    {
        if (!$this->isWritable()) {
            throw new UnwritableException('The stream is no longer writable.');
        }

        $length = strlen($data);
        $written = 0;

        if ($end) {
            $this->writable = false;
        }

        $resource = $this->getResource();

        try {
            if ($this->writeQueue->isEmpty()) {
                if (0 === $length) {
                    return $written;
                }

                // Error reporting suppressed since fwrite() emits E_WARNING if the pipe is broken or the buffer is full.
                $written = @fwrite($resource, $data, self::CHUNK_SIZE);

                if (false === $written) {
                    $message = 'Failed to write to stream.';
                    if ($error = error_get_last()) {
                        $message .= sprintf(' Errno: %d; %s', $error['type'], $error['message']);
                    }
                    throw new FailureException($message);
                }

                if ($length <= $written) {
                    return $written;
                }

                $data = substr($data, $written);
            }

            if (null === $this->await) {
                $this->await = $this->createAwait($resource, $this->writeQueue);
                $this->await->listen($timeout);
            } elseif (!$this->await->isPending()) {
                $this->await->listen($timeout);
            }

            $delayed = new Delayed($this->onCancelled);
            $this->writeQueue->push([$data, $written, $timeout, $delayed]);

            return yield $delayed;
        } catch (Throwable $exception) {
            $this->free($exception);
            throw $exception;
        } finally {
            if ($end && is_resource($resource)) {
                stream_socket_shutdown($resource, STREAM_SHUT_WR);
            }
        }
    }

    /**
     * {@inheritdoc}
     */
    public function end(string $data = '', float $timeout = 0): \Generator
    {
        return $this->send($data, $timeout, true);
    }

    /**
     * @coroutine
     *
     * Returns a coroutine that is fulfilled when the stream is ready to receive data (output buffer is not full).
     *
     * @param float|int $timeout Number of seconds until the returned promise is rejected with a TimeoutException
     *     if the data cannot be written to the stream. Use null for no timeout.
     *
     * @return \Generator
     *
     * @resolve int Always resolves with 0.
     *
     * @throws \Icicle\Stream\Exception\UnwritableException If the stream is no longer writable.
     * @throws \Icicle\Stream\Exception\ClosedException If the stream has been closed.
     */
    public function await(float $timeout = 0): \Generator
    {
        if (!$this->isWritable()) {
            throw new UnwritableException('The stream is no longer writable.');
        }

        if (null === $this->await) {
            $this->await = $this->createAwait($this->getResource(), $this->writeQueue);
            $this->await->listen($timeout);
        } elseif (!$this->await->isPending()) {
            $this->await->listen($timeout);
        }

        $delayed = new Delayed($this->onCancelled);
        $this->writeQueue->push(['', 0, $timeout, $delayed]);

        try {
            return yield $delayed;
        } catch (Throwable $exception) {
            $this->free($exception);
            throw $exception;
        }
    }

    /**
     * {@inheritdoc}
     */
    public function isWritable(): bool
    {
        return $this->writable;
    }

    /**
     * {@inheritdoc}
     *
     * @param float|int $timeout Timeout for await if a write was pending.
     */
    public function rebind(float $timeout = 0)
    {
        if (null !== $this->await) {
            $pending = $this->await->isPending();
            $this->await->free();

            $this->await = $this->createAwait($this->getResource(), $this->writeQueue);

            if ($pending) {
                $this->await->listen($timeout);
            }
        }
    }

    /**
     * @param resource $resource
     * @param \SplQueue $writeQueue
     *
     * @return \Icicle\Loop\Watcher\Io
     */
    private function createAwait($resource, \SplQueue $writeQueue): Io
    {
        return Loop\await($resource, static function ($resource, bool $expired, Io $await) use ($writeQueue) {
            /** @var \Icicle\Awaitable\Delayed $delayed */
            list($data, $previous, $timeout, $delayed) = $writeQueue->shift();

            if ($expired) {
                $delayed->reject(new TimeoutException('Writing to the socket timed out.'));
                return;
            }

            $length = strlen($data);

            if (0 === $length) {
                $delayed->resolve($previous);
            } else {
                // Error reporting suppressed since fwrite() emits E_WARNING if the pipe is broken or the buffer is full.
                $written = @fwrite($resource, $data, self::CHUNK_SIZE);

                if (false === $written || 0 === $written) {
                    $message = 'Failed to write to stream.';
                    if ($error = error_get_last()) {
                        $message .= sprintf(' Errno: %d; %s', $error['type'], $error['message']);
                    }
                    $delayed->reject(new FailureException($message));
                    return;
                }

                if ($length <= $written) {
                    $delayed->resolve($written + $previous);
                } else {
                    $data = substr($data, $written);
                    $written += $previous;
                    $writeQueue->unshift([$data, $written, $timeout, $delayed]);
                }
            }

            if (!$writeQueue->isEmpty()) {
                list( , , $timeout) = $writeQueue->bottom();
                $await->listen($timeout);
            }
        });
    }
}


<?php
/**
 *     This is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 *
 *     This is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU General Public License for more details.
 *
 *     You should have received a copy of the GNU General Public License
 *     along with this.  If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * User: Evren Yurtesen
 * Date: 04-Jul-16
 * Time: 5:14 PM
 */

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateGeonamesGeonamesTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('geonames_geonames', function (Blueprint $table) {
            $table->integer('geoname_id')->primary()->unsigned();
            $table->string('name', 200);
            $table->string('ascii_name', 200)->nullable();
            $table->string('alternate_names', 10000)->nullable();
            $table->decimal('latitude', 7, 5);
            $table->decimal('longitude', 8, 5);
            $table->char('feature_class', 1)->nullable();
            $table->string('feature_code', 10)->nullable();
            $table->char('country_code', 2);
            $table->string('cc2', 200)->nullable();
            $table->string('admin1_code', 20)->nullable();
            $table->string('admin2_code', 80)->nullable();
            $table->string('admin3_code', 20)->nullable();
            $table->string('admin4_code', 20)->nullable();
            $table->bigInteger('population')->unsigned()->nullable();
            $table->integer('elevation')->nullable();
            $table->integer('dem')->nullable();
            $table->string('timezone_id', 40)->index()->nullable();
            $table->foreign('timezone_id')->references('timezone_id')->on('geonames_timezones')->onUpdate('cascade')->onDelete('cascade');
            $table->date('modified_at');
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::drop('geonames_geonames');
    }
}


<?php

/*
 * This file is part of the stream package for Icicle, a library for writing asynchronous code in PHP.
 *
 * @copyright 2014-2015 Aaron Piotrowski. All rights reserved.
 * @license MIT See the LICENSE file that was distributed with this source code for more information.
 */

namespace Icicle\Stream\Text;

use Icicle\Exception\{InvalidArgumentError, UnsupportedError};
use Icicle\Stream;
use Icicle\Stream\{ReadableStream, Structures\Buffer};

/**
 * Reads text from a stream.
 *
 * Requires mbstring to be available to do proper character decoding.
 */
class TextReader
{
    /**
     * @var \Icicle\Stream\ReadableStream The stream to read from.
     */
    private $stream;

    /**
     * @var string The name of the character encoding to use.
     */
    private $encoding;

    /**
     * @var \Icicle\Stream\Structures\Buffer
     */
    private $buffer;

    /**
     * @var string The string of bytes representing a newline in the configured encoding.
     */
    private $newLine;

    /**
     * Creates a new stream reader for a given stream.
     *
     * @param \Icicle\Stream\ReadableStream $stream The stream to read from.
     * @param string $encoding The character encoding to use.
     *
     * @throws \Icicle\Exception\UnsupportedError Thrown if the mbstring extension is not loaded.
     */
    public function __construct(ReadableStream $stream, string $encoding = 'UTF-8')
    {
        if (!extension_loaded('mbstring')) {
            throw new UnsupportedError('The mbstring extension is not loaded.');
        }

        if (!in_array($encoding, mb_list_encodings())) {
            throw new InvalidArgumentError("The encoding '$encoding' is not available.");
        }

        $this->stream = $stream;
        $this->encoding = $encoding;
        $this->buffer = new Buffer();
        $this->newLine = mb_convert_encoding("\n", $encoding, 'UTF-8');
    }

    /**
     * Gets the underlying stream.
     *
     * @return \Icicle\Stream\ReadableStream
     */
    public function getStream(): ReadableStream
    {
        return $this->stream;
    }

    /**
     * Determines if the stream is still open.
     *
     * @return bool
     */
    public function isOpen(): bool
    {
        return $this->stream->isOpen();
    }

    /**
     * Closes the stream reader and the underlying stream.
     */
    public function close()
    {
        $this->stream->close();
    }

    /**
     * @coroutine
     *
     * Returns the next sequence of characters without consuming them.
     *
     * @param int $length The number of characters to peek.
     * @param float|int $timeout Number of seconds until the returned promise is rejected with a TimeoutException
     *     if no data is received. Use 0 for no timeout.
     *
     * @return \Generator
     *
     * @resolve string String of characters read from the stream.
     *
     * @throws \Icicle\Awaitable\Exception\TimeoutException If the operation times out.
     * @throws \Icicle\Stream\Exception\UnreadableException If the stream is no longer readable.
     * @throws \Icicle\Stream\Exception\ClosedException If the stream is unexpectedly closed.
     */
    public function peek(int $length = 1, float $timeout = 0): \Generator
    {
        // Read chunks of bytes until we reach the desired length.
        while (mb_strlen((string) $this->buffer, $this->encoding) < $length && $this->stream->isReadable()) {
            $this->buffer->push(yield from $this->stream->read(0, null, $timeout));
        }

        return mb_substr((string) $this->buffer, 0, min($length, $this->buffer->getLength()), $this->encoding);
    }

    /**
     * @coroutine
     *
     * Reads a specific number of characters from the stream.
     *
     * @param int $length The number of characters to read.
     * @param float|int $timeout Number of seconds until the returned promise is rejected with a TimeoutException
     *     if no data is received. Use 0 for no timeout.
     *
     * @return \Generator
     *
     * @resolve string String of characters read from the stream.
     *
     * @throws \Icicle\Awaitable\Exception\TimeoutException If the operation times out.
     * @throws \Icicle\Stream\Exception\UnreadableException If the stream is no longer readable.
     * @throws \Icicle\Stream\Exception\ClosedException If the stream is unexpectedly closed.
     */
    public function read(int $length = 1, float $timeout = 0): \Generator
    {
        return $this->buffer->shift(strlen(yield from $this->peek($length, $timeout)));
    }

    /**
     * @coroutine
     *
     * Reads a single line from the stream.
     *
     * Reads from the stream until a newline is reached or the stream is closed.
     * The newline characters are included in the returned string. If reading ends
     * in the middle of a character, the trailing bytes are not consumed.
     *
     * @param float|int $timeout Number of seconds until the returned promise is rejected with a TimeoutException
     *     if no data is received. Use 0 for no timeout.
     *
     * @return \Generator
     *
     * @resolve string A line of text read from the stream.
     *
     * @throws \Icicle\Awaitable\Exception\TimeoutException If the operation times out.
     * @throws \Icicle\Stream\Exception\UnreadableException If the stream is no longer readable.
     * @throws \Icicle\Stream\Exception\ClosedException If the stream is unexpectedly closed.
     */
    public function readLine(float $timeout = 0): \Generator
    {
        $newLineSize = strlen($this->newLine);

        // Check if a new line is already in the buffer.
        if (($pos = $this->buffer->search($this->newLine)) !== false) {
            return $this->buffer->shift($pos + $newLineSize);
        }

        $this->buffer->push(yield from Stream\readUntil($this->stream, $this->newLine, 0, $timeout));

        if (($pos = $this->buffer->search($this->newLine)) !== false) {
            return $this->buffer->shift($pos + $newLineSize);
        }

        return $this->buffer->shift(strlen(mb_strcut((string) $this->buffer, 0, null, $this->encoding)));
    }

    /**
     * @coroutine
     *
     * Reads all characters from the stream until the end of the stream is reached.
     *
     * If the stream ends in the middle of a character, the left over bytes will be discarded.
     *
     * @param int $maxLength The maximum number of bytes to read.
     * @param float|int $timeout Number of seconds until the returned promise is rejected with a TimeoutException
     *     if no data is received. Use 0 for no timeout.
     *
     * @return \Generator
     *
     * @resolve string The contents of the stream.
     *
     * @throws \Icicle\Awaitable\Exception\TimeoutException If the operation times out.
     * @throws \Icicle\Stream\Exception\UnreadableException If the stream is no longer readable.
     * @throws \Icicle\Stream\Exception\ClosedException If the stream is unexpectedly closed.
     */
    public function readAll(int $maxlength = 0, float $timeout = 0): \Generator
    {
        $this->buffer->push(yield from Stream\readAll($this->stream, $maxlength, $timeout));
        return $this->buffer->shift(strlen(mb_strcut((string) $this->buffer, 0, null, $this->encoding)));
    }

    /**
     * @coroutine
     *
     * Reads and parses characters from the stream according to a format.
     *
     * The format string is of the same format as `sscanf()`.
     *
     * @param string $format The parse format.
     * @param float|int $timeout Number of seconds until the returned promise is rejected with a TimeoutException
     *     if no data is received. Use 0 for no timeout.
     *
     * @return \Generator
     *
     * @resolve array An array of parsed values.
     *
     * @throws \Icicle\Awaitable\Exception\TimeoutException If the operation times out.
     * @throws \Icicle\Stream\Exception\UnreadableException If the stream is no longer readable.
     * @throws \Icicle\Stream\Exception\ClosedException If the stream is unexpectedly closed.
     *
     * @see http://php.net/sscanf
     */
    public function scan(string $format, float $timeout = 0): \Generator
    {
        // Read from the stream chunk by chunk, attempting to satisfy the format
        // string each time until the format successfully parses or the end of
        // the stream is reached.
        while (true) {
            $result = sscanf((string) $this->buffer, $format . '%n');
            $length = $result ? array_pop($result) : null;

            // If the format string was satisfied, consume the used characters and
            // return the parsed results.
            if ($length !== null && $length < $this->buffer->getLength()) {
                $this->buffer->shift($length);
                return $result;
            }

            // Read more into the buffer if possible.
            if ($this->stream->isReadable()) {
                $this->buffer->push(yield from $this->stream->read(0, null, $timeout));
            } else {
                // Format string can't be satisfied.
                return [];
            }
        }
    }
}


<?php
/**
 *     This is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 *
 *     This is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU General Public License for more details.
 *
 *     You should have received a copy of the GNU General Public License
 *     along with this.  If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * User: Evren Yurtesen
 * Date: 04-Jul-16
 * Time: 5:14 PM
 */

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateGeonamesIsoLanguageCodesTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('geonames_iso_language_codes', function (Blueprint $table) {
            $table->char('iso_639_3',3)->primary();
            $table->char('iso_639_2',3)->unique()->nullable();
            $table->char('iso_639_1',2)->unique()->nullable();
            $table->string('language_name', 200);
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::drop('geonames_iso_language_codes');
    }
}


<?php

/*
 * This file is part of the stream package for Icicle, a library for writing asynchronous code in PHP.
 *
 * @copyright 2015 Aaron Piotrowski. All rights reserved.
 * @license MIT See the LICENSE file that was distributed with this source code for more information.
 */

namespace Icicle\Stream;

use Icicle\Exception\InvalidArgumentError;

abstract class StreamResource implements Resource
{
    /**
     * Stream resource or null if the resource has been closed.
     *
     * @var resource|null
     */
    private $resource;

    /**
     * @var bool
     */
    private $autoClose = true;

    /**
     * @param resource $resource PHP stream resource.
     * @param bool $autoClose True to close the resource on destruct, false to leave it open.
     *
     * @throws \Icicle\Exception\InvalidArgumentError If a non-resource is given.
     */
    public function __construct($resource, bool $autoClose = true)
    {
        if (!is_resource($resource) || get_resource_type($resource) !== 'stream') {
            throw new InvalidArgumentError('Invalid resource given to constructor!');
        }

        $this->resource = $resource;
        $this->autoClose = $autoClose;

        stream_set_blocking($this->resource, 0);
    }

    public function __destruct()
    {
        if ($this->autoClose && is_resource($this->resource)) {
            fclose($this->resource);
        }
    }

    /**
     * Determines if the stream resource is still open.
     *
     * @return bool
     */
    public function isOpen(): bool
    {
        return null !== $this->resource;
    }

    /**
     * Closes the socket.
     */
    public function close()
    {
        if (is_resource($this->resource)) {
            fclose($this->resource);
        }

        $this->resource = null;
        $this->autoClose = false;
    }

    /**
     * Returns the stream resource or null if the resource has been closed.
     *
     * @return resource|null
     */
    public function getResource()
    {
        return $this->resource;
    }
}


<?php
/**
 *     This is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 *
 *     This is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU General Public License for more details.
 *
 *     You should have received a copy of the GNU General Public License
 *     along with this.  If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * User: Evren Yurtesen
 * Date: 04-Jul-16
 * Time: 2:16 PM
 */

namespace Yurtesen\Geonames;

use Illuminate\Support\ServiceProvider;

class GeonamesServiceProvider extends ServiceProvider
{
    /**
     * Artisan commands
     *
     * @var array
     */
    protected $serviceCommands = [
        'Yurtesen\Geonames\Console\Download',
        'Yurtesen\Geonames\Console\Install',
        'Yurtesen\Geonames\Console\Seed',
    ];

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

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

        $this->publishes([
            __DIR__ . '/database/migrations' => base_path('database/migrations')
        ], 'migrations');

        $this->publishes([
            __DIR__ . '/config/geonames.php' =>  base_path('config/geonames.php')
        ], 'config');

        $this->mergeConfigFrom(realpath(__DIR__ . '/config/geonames.php'), 'geonames');
    }

    /**
     * Register the service provider.
     *
     * @return void
     */
    public function register()
    {
        $this->commands($this->serviceCommands);
    }

    /**
     * Get the services provided by the provider.
     *
     * @return array
     */
    public function provides()
    {
        return $this->serviceCommands;
    }

}


<?php
/**
 *     This is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 *
 *     This is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU General Public License for more details.
 *
 *     You should have received a copy of the GNU General Public License
 *     along with this.  If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * User: Evren Yurtesen
 * Date: 04-Jul-16
 * Time: 5:14 PM
 */

use Illuminate\Database\Schema\Blueprint;
use Illuminate\Database\Migrations\Migration;

class CreateGeonamesTimezonesTable extends Migration
{
    /**
     * Run the migrations.
     *
     * @return void
     */
    public function up()
    {
        Schema::create('geonames_timezones', function (Blueprint $table) {
            $table->string('timezone_id', 40)->primary();
            $table->char('country_code', 2)->index();
            // We are adding this constraint from geonames_country_infos migration
            //$table->foreign('country_code')->references('iso')->on('geonames_country_infos')->onUpdate('cascade')->onDelete('cascade');
            $table->decimal('gmt_offset', 2, 1);
            $table->decimal('dst_offset', 2, 1);
            $table->decimal('raw_offset', 2, 1);
        });
    }

    /**
     * Reverse the migrations.
     *
     * @return void
     */
    public function down()
    {
        Schema::drop('geonames_timezones');
    }
}


<?php
/**
 *     This is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 *
 *     This is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU General Public License for more details.
 *
 *     You should have received a copy of the GNU General Public License
 *     along with this.  If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * User: Evren Yurtesen
 * Date: 06-Jul-16
 * Time: 11:30 AM
 */

namespace Yurtesen\Geonames\Models;

use DB;
use Illuminate\Database\Eloquent\Model;

/**
 * Yurtesen\Geonames\Models\GeonamesGeoname
 *
 * @property integer $geoname_id
 * @property string $name
 * @property string $ascii_name
 * @property string $alternate_names
 * @property float $latitude
 * @property float $longitude
 * @property string $feature_class
 * @property string $feature_code
 * @property string $country_code
 * @property string $cc2
 * @property string $admin1_code
 * @property string $admin2_code
 * @property string $admin3_code
 * @property string $admin4_code
 * @property integer $population
 * @property integer $elevation
 * @property integer $dem
 * @property string $timezone_id
 * @property string $modified_at
 * @property-read \Yurtesen\Geonames\Models\GeonamesAlternateName $alternateName
 * @property-read \Yurtesen\Geonames\Models\GeonamesTimezone $timeZone
 * @property-read \Yurtesen\Geonames\Models\GeonamesCountryInfo $countryInfo
 * @method static \Illuminate\Database\Query\Builder|\Yurtesen\Geonames\Models\GeonamesGeoname whereGeonameId($value)
 * @method static \Illuminate\Database\Query\Builder|\Yurtesen\Geonames\Models\GeonamesGeoname whereName($value)
 * @method static \Illuminate\Database\Query\Builder|\Yurtesen\Geonames\Models\GeonamesGeoname whereAsciiName($value)
 * @method static \Illuminate\Database\Query\Builder|\Yurtesen\Geonames\Models\GeonamesGeoname whereAlternateNames($value)
 * @method static \Illuminate\Database\Query\Builder|\Yurtesen\Geonames\Models\GeonamesGeoname whereLatitude($value)
 * @method static \Illuminate\Database\Query\Builder|\Yurtesen\Geonames\Models\GeonamesGeoname whereLongitude($value)
 * @method static \Illuminate\Database\Query\Builder|\Yurtesen\Geonames\Models\GeonamesGeoname whereFeatureClass($value)
 * @method static \Illuminate\Database\Query\Builder|\Yurtesen\Geonames\Models\GeonamesGeoname whereFeatureCode($value)
 * @method static \Illuminate\Database\Query\Builder|\Yurtesen\Geonames\Models\GeonamesGeoname whereCountryCode($value)
 * @method static \Illuminate\Database\Query\Builder|\Yurtesen\Geonames\Models\GeonamesGeoname whereCc2($value)
 * @method static \Illuminate\Database\Query\Builder|\Yurtesen\Geonames\Models\GeonamesGeoname whereAdmin1Code($value)
 * @method static \Illuminate\Database\Query\Builder|\Yurtesen\Geonames\Models\GeonamesGeoname whereAdmin2Code($value)
 * @method static \Illuminate\Database\Query\Builder|\Yurtesen\Geonames\Models\GeonamesGeoname whereAdmin3Code($value)
 * @method static \Illuminate\Database\Query\Builder|\Yurtesen\Geonames\Models\GeonamesGeoname whereAdmin4Code($value)
 * @method static \Illuminate\Database\Query\Builder|\Yurtesen\Geonames\Models\GeonamesGeoname wherePopulation($value)
 * @method static \Illuminate\Database\Query\Builder|\Yurtesen\Geonames\Models\GeonamesGeoname whereElevation($value)
 * @method static \Illuminate\Database\Query\Builder|\Yurtesen\Geonames\Models\GeonamesGeoname whereDem($value)
 * @method static \Illuminate\Database\Query\Builder|\Yurtesen\Geonames\Models\GeonamesGeoname whereTimezoneId($value)
 * @method static \Illuminate\Database\Query\Builder|\Yurtesen\Geonames\Models\GeonamesGeoname whereModifiedAt($value)
 * @method static \Illuminate\Database\Query\Builder|\Yurtesen\Geonames\Models\GeonamesGeoname admin1()
 * @method static \Illuminate\Database\Query\Builder|\Yurtesen\Geonames\Models\GeonamesGeoname addCountryInfo()
 * @method static \Illuminate\Database\Query\Builder|\Yurtesen\Geonames\Models\GeonamesGeoname city($name = null, $featureCodes = array())
 * @method static \Illuminate\Database\Query\Builder|\Yurtesen\Geonames\Models\GeonamesGeoname country($name = null, $featureCodes = array())
 * @method static \Illuminate\Database\Query\Builder|\Yurtesen\Geonames\Models\GeonamesGeoname searchByFeature($name = null, $feature_class = null, $featureCodes = null)
 * @mixin \Eloquent
 */
class GeonamesGeoname extends Model
{
    /**
     * The attributes that are mass assignable.
     *
     * @var array
     */
    protected $fillable = [
    ];

    /**
     * The attributes that should be hidden for arrays.
     *
     * @var array
     */
    protected $hidden = [
    ];

    /**
     * The primary key for the model.
     *
     * @var string
     */
    protected $primaryKey = 'geoname_id';

    /**
     * Indicates if the IDs are auto-incrementing.
     *
     * @var bool
     */
    public $incrementing = false;

    /**
     * Indicates if the model should be timestamped.
     *
     * @var bool
     */
    public $timestamps = false;

    /**
     *  Most useful fields which we want in every result which utilize our scopes
     *
     * @var array
     */
    private $usefulScopeColumns = [
        'geonames_geonames.geoname_id',
        'geonames_geonames.name',
        'geonames_geonames.country_code'
    ];

    /**
     * One-to-One relation with GeonamesAlternateNames
     *
     * @return \Illuminate\Database\Eloquent\Relations\HasOne
     */
    public function alternateName()
    {
        return $this->hasMany(GeonamesAlternateName::class, 'geoname_id', 'geoname_id');
    }

    /**
     * One-to-One relation with GeonamesTimezones
     *
     * @return \Illuminate\Database\Eloquent\Relations\HasOne
     */
    public function timeZone()
    {
        return $this->hasOne(GeonamesTimezone::class, 'timezone_id', 'timezone_id');
    }

    /**
     * One-to-One relation with GeonamesCountryInfos
     *
     * @return \Illuminate\Database\Eloquent\Relations\HasOne
     */
    public function countryInfo()
    {
        return $this->hasOne(GeonamesCountryInfo::class, 'iso', 'country_code');
    }

    /**
     * Return admin1 information in result
     *
     * @param \Illuminate\Database\Query\Builder $query
     * @return \Illuminate\Database\Query\Builder
     */
    public function scopeAdmin1($query)
    {
        $table = 'geonames_geonames';

        if (!isset($query->getQuery()->columns))
            $query = $query->addSelect($this->usefulScopeColumns);

        $query = $query
            ->leftJoin('geonames_admin1_codes as admin1', 'admin1.code', '=',
                DB::raw('CONCAT_WS(\'.\',' .
                    $table . '.country_code,' .
                    $table . '.admin1_code)')
            )
            ->addSelect(
                'admin1.geoname_id as admin1_geoname_id',
                'admin1.name as admin1_name'
            );

        return $query;
    }

    /**
     * Return country information in result
     *
     * @param \Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder $query
     * @return \Illuminate\Database\Query\Builder
     */
    public function scopeAddCountryInfo($query)
    {
        $table = 'geonames_geonames';

        if (!isset($query->getQuery()->columns))
            $query = $query->addSelect($this->usefulScopeColumns);

        $query = $query
            ->leftJoin('geonames_country_infos as country_info', $table . '.country_code', '=',
                'country_info.iso'
            )
            ->addSelect(
                'country_info.geoname_id as country_info_geoname_id',
                'country_info.country as country_info_country'
            );

        return $query;
    }

    /**
     * Build a query to find major cities. Accepts wildcards eg. 'Helsin%'
     *
     * Suggested index for search:
     * ALTER TABLE geonames_geonames ADD INDEX geonames_geonames_feature_name_index
     *                                                          (`feature_class`,`feature_code`,`name`);
     * and if you will limit queries by country you should also use:
     * ALTER TABLE geonames_geonames ADD INDEX geonames_geonames_country_feature_name_index
     *                                                          (`country_code`,`feature_class`,`feature_code`,`name`);
     *
     *
     * @param \Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder $query
     * @param String $name
     * @param array $featureCodes List of feature codes to use when returning results
     *                            defaults to ['PPLC','PPLA','PPLA2', 'PPLA3']
     * @return \Illuminate\Database\Query\Builder
     */
    public function scopeCity($query, $name = null, $featureCodes = ['PPLC', 'PPLA', 'PPLA2', 'PPLA3'])
    {
        return $this->scopeSearchByFeature($query,$name,'P',$featureCodes);
    }

    /**
     * Build a query to find major ccountries. Accepts wildcards eg. '%Finland%'
     *
     * Suggested index for search:
     * ALTER TABLE geonames_geonames ADD INDEX geonames_geonames_feature_name_index
     *                                                          (`feature_class`,`feature_code`,`name`);
     *
     * @param \Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder $query
     * @param String $name
     * @param array $featureCodes List of feature codes to use when returning results
     *                            defaults to ['PCLI']
     * @return \Illuminate\Database\Query\Builder
     */
    public function scopeCountry($query, $name = null, $featureCodes = ['PCLI'])
    {
        return $this->scopeSearchByFeature($query,$name,'A',$featureCodes);
    }

    /**
     * Generic query used by scopes, but you can call it with custom feataure codes.
     *
     * @param \Illuminate\Database\Query\Builder|\Illuminate\Database\Eloquent\Builder $query
     * @param String $name
     * @param String $feature_class The 1 character feature class
     * @param array $featureCodes List of feature codes to use when returning results
     * @return \Illuminate\Database\Query\Builder
     */
    public function scopeSearchByFeature($query, $name = null, $feature_class=null, $featureCodes = null)
    {
        $table = 'geonames_geonames';

        if (!isset($query->getQuery()->columns))
            $query = $query->addSelect($this->usefulScopeColumns);

        if ($name !== null)
            $query = $query->where($table . '.name', 'LIKE', $name);

        $query = $query
            ->where($table . '.feature_class', $feature_class)
            ->whereIn($table . '.feature_code', $featureCodes);

        return $query;
    }

}


<?php
/**
 *     This is free software: you can redistribute it and/or modify
 *     it under the terms of the GNU General Public License as published by
 *     the Free Software Foundation, either version 3 of the License, or
 *     (at your option) any later version.
 *
 *     This is distributed in the hope that it will be useful,
 *     but WITHOUT ANY WARRANTY; without even the implied warranty of
 *     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *     GNU General Public License for more details.
 *
 *     You should have received a copy of the GNU General Public License
 *     along with this.  If not, see <http://www.gnu.org/licenses/>.
 */

/**
 * User: Evren Yurtesen
 * Date: 05-Jul-16
 * Time: 3:26 PM
 */

namespace Yurtesen\Geonames\Console;

use RuntimeException;
use Schema;
use Illuminate\Console\Command;

class Seed extends Command
{
    /**
     * This includes some common functions used by commands
     */
    use CommandTrait;

    /**
     * The name and signature of the console command.
     *
     * @var string
     */
    protected $signature = 'geonames:seed
                            {--refresh : Truncate tables and re-insert data from scratch}
                            {--update-files : Update geonames files before inserting data to database}
                            {--table= : Only import the given database table}
                           ';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = 'Seed the database from geonames files';

    /**
     * Execute the console command.
     *
     * @throws RuntimeException;
     * @return mixed
     */
    public function handle()
    {
        $updateFiles = $this->input->getOption('update-files');
        $refresh = $this->input->getOption('refresh');
        $table = $this->input->getOption('table');

        if (isset($table)) {
            foreach ($this->files as $name => $file) {
                if ($file['table'] == $table) {
                    $this->downloadFile($name, $updateFiles);
                    $this->parseGeonamesText($name, $refresh);
                    return;
                }
            }
            $this->line('<error>Table Not Found: </error> Table '.$table.'not found in configuration');
            return;
        } else {
            $this->downloadAllFiles($updateFiles);
            // Check if we have all the tables
            foreach ($this->getFilesArray() as $name => $file) {
                if (Schema::hasTable($file['table'])) {
                    $this->parseGeonamesText($name, $refresh);
                } else {
                    throw new RuntimeException($file['table'] . ' table not found. Did you run geoname:install then run migrate ?');
                }
            }
        }
        $this->line('<info>Finished : </info> Requested actions has been completed!');
    }
}


<?php
namespace Neos\RedirectHandler\NeosAdapter\Service;

/*
 * This file is part of the Neos.RedirectHandler.NeosAdapter package.
 *
 * (c) Contributors of the Neos Project - www.neos.io
 *
 * This package is Open Source Software. For the full copyright and license
 * information, please view the LICENSE file which was distributed with this
 * source code.
 */

use Neos\ContentRepository\Domain\Factory\NodeFactory;
use Neos\ContentRepository\Domain\Model\NodeInterface;
use Neos\ContentRepository\Domain\Model\Workspace;
use Neos\ContentRepository\Domain\Service\ContentDimensionCombinator;
use Neos\ContentRepository\Domain\Service\ContextFactoryInterface;
use Neos\Flow\Annotations as Flow;
use Neos\Flow\Http\Request;
use Neos\Flow\Log\SystemLoggerInterface;
use Neos\Flow\Mvc\ActionRequest;
use Neos\Flow\Mvc\Routing\RouterCachingService;
use Neos\Flow\Mvc\Routing\UriBuilder;
use Neos\Flow\Persistence\PersistenceManagerInterface;
use Neos\Neos\Domain\Model\Domain;
use Neos\Neos\Domain\Service\ContentContext;
use Neos\RedirectHandler\Storage\RedirectStorageInterface;

/**
 * Service that creates redirects for moved / deleted nodes.
 *
 * Note: This is usually invoked by signals.
 *
 * @Flow\Scope("singleton")
 */
class NodeRedirectService
{
    /**
     * @var UriBuilder
     */
    protected $uriBuilder;

    /**
     * @Flow\Inject
     * @var RedirectStorageInterface
     */
    protected $redirectStorage;

    /**
     * @Flow\Inject
     * @var RouterCachingService
     */
    protected $routerCachingService;

    /**
     * @Flow\Inject
     * @var PersistenceManagerInterface
     */
    protected $persistenceManager;

    /**
     * @Flow\Inject
     * @var ContextFactoryInterface
     */
    protected $contextFactory;

    /**
     * @Flow\Inject
     * @var NodeFactory
     */
    protected $nodeFactory;

    /**
     * @Flow\Inject
     * @var SystemLoggerInterface
     */
    protected $systemLogger;

    /**
     * @Flow\InjectConfiguration(path="statusCode", package="Neos.RedirectHandler")
     * @var array
     */
    protected $defaultStatusCode;

    /**
     * @Flow\Inject
     * @var ContentDimensionCombinator
     */
    protected $contentDimensionCombinator;

    /**
     * @Flow\InjectConfiguration(path="enableRemovedNodeRedirect", package="Neos.RedirectHandler.NeosAdapter")
     * @var array
     */
    protected $enableRemovedNodeRedirect;

    /**
     * @Flow\InjectConfiguration(path="restrictByPathPrefix", package="Neos.RedirectHandler.NeosAdapter")
     * @var array
     */
    protected $restrictByPathPrefix;

    /**
     * @Flow\InjectConfiguration(path="restrictByNodeType", package="Neos.RedirectHandler.NeosAdapter")
     * @var array
     */
    protected $restrictByNodeType;

    /**
     * @var array
     */
    protected $pendingRedirects = [];

    /**
     * Collects the node for redirection if it is a 'Neos.Neos:Document' node and its URI has changed
     *
     * @param NodeInterface $node The node that is about to be published
     * @param Workspace $targetWorkspace
     * @return void
     * @throws \Neos\Flow\Mvc\Routing\Exception\MissingActionNameException
     */
    public function collectPossibleRedirects(NodeInterface $node, Workspace $targetWorkspace)
    {
        $nodeType = $node->getNodeType();
        if ($targetWorkspace->isPublicWorkspace() === false || $nodeType->isOfType('Neos.Neos:Document') === false) {
            return;
        }
        $this->appendNodeAndChildrenDocumentsToPendingRedirects($node, $targetWorkspace);
    }

    /**
     * Creates the queued redirects provided we can find the node.
     *
     * @return void
     * @throws \Neos\Flow\Mvc\Routing\Exception\MissingActionNameException
     */
    public function createPendingRedirects()
    {
        $this->nodeFactory->reset();
        foreach ($this->pendingRedirects as $nodeIdentifierAndWorkspace => $oldUriPerDimensionCombination) {
            list($nodeIdentifier, $workspaceName) = explode('@', $nodeIdentifierAndWorkspace);
            $this->buildRedirects($nodeIdentifier, $workspaceName, $oldUriPerDimensionCombination);
        }

        $this->persistenceManager->persistAll();
    }

    /**
     * @param NodeInterface $node
     * @param Workspace $targetWorkspace
     * @return void
     * @throws \Neos\Flow\Mvc\Routing\Exception\MissingActionNameException
     */
    protected function appendNodeAndChildrenDocumentsToPendingRedirects(NodeInterface $node, Workspace $targetWorkspace)
    {
        $identifierAndWorkspaceKey = $node->getIdentifier() . '@' . $targetWorkspace->getName();
        if (isset($this->pendingRedirects[$identifierAndWorkspaceKey])) {
            return;
        }

        if (!$this->hasNodeUriChanged($node, $targetWorkspace)) {
            return;
        }

        $this->pendingRedirects[$identifierAndWorkspaceKey] = $this->createUriPathsAcrossDimensionsForNode($node->getIdentifier(), $targetWorkspace);

        foreach ($node->getChildNodes('Neos.Neos:Document') as $childNode) {
            $this->appendNodeAndChildrenDocumentsToPendingRedirects($childNode, $targetWorkspace);
        }
    }

    /**
     * @param string $nodeIdentifier
     * @param Workspace $targetWorkspace
     * @return array
     * @throws \Neos\Flow\Mvc\Routing\Exception\MissingActionNameException
     */
    protected function createUriPathsAcrossDimensionsForNode(string $nodeIdentifier, Workspace $targetWorkspace): array
    {
        $result = [];
        foreach ($this->contentDimensionCombinator->getAllAllowedCombinations() as $allowedCombination) {
            $nodeInDimensions = $this->getNodeInWorkspaceAndDimensions($nodeIdentifier, $targetWorkspace->getName(), $allowedCombination);
            if ($nodeInDimensions === null) {
                continue;
            }

            $nodeUriPath = $this->buildUriPathForNode($nodeInDimensions);
            $nodeUriPath = $this->removeContextInformationFromRelativeNodeUri($nodeUriPath);
            $result[] = [
                $nodeUriPath,
                $allowedCombination
            ];
        }

        return $result;
    }

    /**
     * Has the Uri changed at all.
     *
     * @param NodeInterface $node
     * @param Workspace $targetWorkspace
     * @return bool
     * @throws \Neos\Flow\Mvc\Routing\Exception\MissingActionNameException
     */
    protected function hasNodeUriChanged(NodeInterface $node, Workspace $targetWorkspace): bool
    {
        $newUriPath = $this->buildUriPathForNode($node);
        $newUriPath = $this->removeContextInformationFromRelativeNodeUri($newUriPath);

        $nodeInTargetWorkspace = $this->getNodeInWorkspace($node, $targetWorkspace);
        if (!$nodeInTargetWorkspace) {
            return false;
        }
        $oldUriPath = $this->buildUriPathForNode($nodeInTargetWorkspace);
        $oldUriPath = $this->removeContextInformationFromRelativeNodeUri($oldUriPath);

        return ($newUriPath !== $oldUriPath);
    }

    /**
     * Build redirects in all dimensions for a given node.
     *
     * @param string $nodeIdentifier
     * @param string $workspaceName
     * @param $oldUriPerDimensionCombination
     * @return void
     * @throws \Neos\Flow\Mvc\Routing\Exception\MissingActionNameException
     */
    protected function buildRedirects(string $nodeIdentifier, string $workspaceName, array $oldUriPerDimensionCombination)
    {
        foreach ($oldUriPerDimensionCombination as list($oldRelativeUri, $dimensionCombination)) {
            $this->createRedirectFrom($oldRelativeUri, $nodeIdentifier, $workspaceName, $dimensionCombination);
        }
    }

    /**
     * Gets the node in the given dimensions and workspace and redirects the oldUri to the new one.
     *
     * @param string $oldUri
     * @param string $nodeIdentifer
     * @param string $workspaceName
     * @param array $dimensionCombination
     * @return bool
     * @throws \Neos\Flow\Mvc\Routing\Exception\MissingActionNameException
     */
    protected function createRedirectFrom(string $oldUri, string $nodeIdentifer, string $workspaceName, array $dimensionCombination): bool
    {
        $node = $this->getNodeInWorkspaceAndDimensions($nodeIdentifer, $workspaceName, $dimensionCombination);
        if ($node === null) {
            return false;
        }

        if ($this->isRestrictedByNodeType($node) || $this->isRestrictedByPath($node)) {
            return false;
        }

        $newUri = $this->buildUriPathForNode($node);

        if ($node->isRemoved()) {
            return $this->removeNodeRedirectIfNeeded($node, $newUri);
        }

        if ($oldUri === $newUri) {
            return false;
        }

        $hosts = $this->getHostnames($node->getContext());
        $this->flushRoutingCacheForNode($node);
        $statusCode = (integer)$this->defaultStatusCode['redirect'];

        $this->redirectStorage->addRedirect($oldUri, $newUri, $statusCode, $hosts);

        return true;
    }

    /**
     * Removes a redirect
     *
     * @param NodeInterface $node
     * @param string $newUri
     * @return bool
     */
    protected function removeNodeRedirectIfNeeded(NodeInterface $node, string $newUri): bool
    {
        // By default the redirect handling for removed nodes is activated.
        // If it is deactivated in your settings you will be able to handle the redirects on your own.
        // For example redirect to dedicated landing pages for deleted campaign NodeTypes
        if ($this->enableRemovedNodeRedirect) {
            $hosts = $this->getHostnames($node->getContext());
            $this->flushRoutingCacheForNode($node);
            $statusCode = (integer)$this->defaultStatusCode['gone'];
            $this->redirectStorage->addRedirect($newUri, '', $statusCode, $hosts);

            return true;
        }

        return false;
    }

    /**
     * Removes any context information appended to a node Uri.
     *
     * @param string $relativeNodeUri
     * @return string
     */
    protected function removeContextInformationFromRelativeNodeUri(string $relativeNodeUri): string
    {
        // FIXME: Uses the same regexp than the ContentContextBar Ember View, but we can probably find something better.
        return (string)preg_replace('/@[A-Za-z0-9;&,\-_=]+/', '', $relativeNodeUri);
    }

    /**
     * Check if the current node type is restricted by Settings
     *
     * @param NodeInterface $node
     * @return bool
     */
    protected function isRestrictedByNodeType(NodeInterface $node): bool
    {
        if (!isset($this->restrictByNodeType)) {
            return false;
        }

        foreach ($this->restrictByNodeType as $disabledNodeType => $status) {
            if ($status !== true) {
                continue;
            }
            if ($node->getNodeType()->isOfType($disabledNodeType)) {
                $this->systemLogger->log(vsprintf('Redirect skipped based on the current node type (%s) for node %s because is of type %s', [
                    $node->getNodeType()->getName(),
                    $node->getContextPath(),
                    $disabledNodeType
                ]), LOG_DEBUG, null, 'RedirectHandler');

                return true;
            }
        }

        return false;
    }

    /**
     * Check if the current node path is restricted by Settings
     *
     * @param NodeInterface $node
     * @return bool
     */
    protected function isRestrictedByPath(NodeInterface $node): bool
    {
        if (!isset($this->restrictByPathPrefix)) {
            return false;
        }

        foreach ($this->restrictByPathPrefix as $pathPrefix => $status) {
            if ($status !== true) {
                continue;
            }
            $pathPrefix = rtrim($pathPrefix, '/') . '/';
            if (mb_strpos($node->getPath(), $pathPrefix) === 0) {
                $this->systemLogger->log(vsprintf('Redirect skipped based on the current node path (%s) for node %s because prefix matches %s', [
                    $node->getPath(),
                    $node->getContextPath(),
                    $pathPrefix
                ]), LOG_DEBUG, null, 'RedirectHandler');

                return true;
            }
        }

        return false;
    }

    /**
     * Collects all hostnames from the Domain entries attached to the current site.
     *
     * @param ContentContext $contentContext
     * @return array
     */
    protected function getHostnames(ContentContext $contentContext): array
    {
        $domains = [];
        $site = $contentContext->getCurrentSite();
        if ($site === null) {
            return $domains;
        }

        foreach ($site->getActiveDomains() as $domain) {
            /** @var Domain $domain */
            $domains[] = $domain->getHostname();
        }

        return $domains;
    }

    /**
     * Removes all routing cache entries for the given $nodeData
     *
     * @param NodeInterface $node
     * @return void
     */
    protected function flushRoutingCacheForNode(NodeInterface $node)
    {
        $nodeData = $node->getNodeData();
        $nodeDataIdentifier = $this->persistenceManager->getIdentifierByObject($nodeData);
        if ($nodeDataIdentifier === null) {
            return;
        }
        $this->routerCachingService->flushCachesByTag($nodeDataIdentifier);
    }

    /**
     * Creates a (relative) URI for the given $nodeContextPath removing the "@workspace-name" from the result
     *
     * @param NodeInterface $node
     * @return string the resulting (relative) URI
     * @throws \Neos\Flow\Mvc\Routing\Exception\MissingActionNameException
     */
    protected function buildUriPathForNode(NodeInterface $node): string
    {
        return $this->getUriBuilder()
            ->uriFor('show', ['node' => $node], 'Frontend\\Node', 'Neos.Neos');
    }

    /**
     * Creates an UriBuilder instance for the current request
     *
     * @return UriBuilder
     */
    protected function getUriBuilder(): UriBuilder
    {
        if ($this->uriBuilder !== null) {
            return $this->uriBuilder;
        }

        $httpRequest = Request::createFromEnvironment();
        $actionRequest = new ActionRequest($httpRequest);
        $this->uriBuilder = new UriBuilder();
        $this->uriBuilder
            ->setRequest($actionRequest);
        $this->uriBuilder
            ->setFormat('html')
            ->setCreateAbsoluteUri(false);

        return $this->uriBuilder;
    }

    /**
     * @param NodeInterface $node
     * @param Workspace $targetWorkspace
     * @return NodeInterface|null
     */
    protected function getNodeInWorkspace(NodeInterface $node, Workspace $targetWorkspace)
    {
        return $this->getNodeInWorkspaceAndDimensions($node->getIdentifier(), $targetWorkspace->getName(), $node->getContext()->getDimensions());
    }

    /**
     * @param string $nodeIdentifier
     * @param string $workspaceName
     * @param array $dimensionCombination
     * @return NodeInterface|null
     */
    protected function getNodeInWorkspaceAndDimensions(string $nodeIdentifier, string $workspaceName, array $dimensionCombination)
    {
        $context = $this->contextFactory->create([
            'workspaceName' => $workspaceName,
            'dimensions' => $dimensionCombination,
            'invisibleContentShown' => true,
        ]);

        return $context->getNodeByIdentifier($nodeIdentifier);
    }
}


<?php

/**
 * Contao Open Source CMS
 *
 * Copyright (c) 2005-2015 Leo Feyer
 *
 * @license LGPL-3.0+
 */

/**
 * Table tl_short_urls
 */
$GLOBALS['TL_DCA']['tl_short_urls'] = array
(

    // Config
    'config' => array
    (
        'dataContainer'               => 'Table',
        'enableVersioning'            => true,
        'sql' => array
        (
            'keys' => array
            (
                'id' => 'primary',
                'name,disable' => 'index'
            )
        )
    ),

    // List
    'list' => array
    (
        'sorting' => array
        (
            'mode'                    => 1,
            'fields'                  => array('name'),
            'flag'                    => 1,
            'panelLayout'             => 'filter;search,limit'
        ),
        'label' => array
        (
            'fields'                  => array('name'),
            'format'                  => '%s',
            'label_callback'          => array('tl_short_urls', 'labelCallback'),
        ),
        'operations' => array
        (
            'edit' => array
            (
                'label'               => &$GLOBALS['TL_LANG']['tl_short_urls']['editheader'],
                'href'                => 'act=edit',
                'icon'                => 'edit.gif'
            ),
            'toggle' => array
            (
                'label'               => &$GLOBALS['TL_LANG']['tl_short_urls']['toggle'],
                'icon'                => 'visible.gif',
                'attributes'          => 'onclick="Backend.getScrollOffset();return AjaxRequest.toggleVisibility(this,%s)"',
                'button_callback'     => array('tl_short_urls', 'toggleIcon')
            ),
            'delete' => array
            (
                'label'               => &$GLOBALS['TL_LANG']['tl_short_urls']['delete'],
                'href'                => 'act=delete',
                'icon'                => 'delete.gif',
                'attributes'          => 'onclick="if(!confirm(\'' . $GLOBALS['TL_LANG']['MSC']['deleteConfirm'] . '\'))return false;Backend.getScrollOffset()"'
            ),
            'show' => array
            (
                'label'               => &$GLOBALS['TL_LANG']['tl_short_urls']['show'],
                'href'                => 'act=show',
                'icon'                => 'show.gif'
            )
        )
    ),

    // Palettes
    'palettes' => array
    (
        'default'                     => '{shorturl_legend},name,domain,target,redirect,disable'
    ),

    // Fields
    'fields' => array
    (
        'id' => array
        (
            'sql'                     => "int(10) unsigned NOT NULL auto_increment"
        ),
        'tstamp' => array
        (
            'sql'                     => "int(10) unsigned NOT NULL default '0'"
        ),
        'name' => array
        (
            'label'                   => &$GLOBALS['TL_LANG']['tl_short_urls']['name'],
            'exclude'                 => true,
            'search'                  => true,
            'inputType'               => 'text',
            'eval'                    => array('maxlength'=>255, 'mandatory'=>true, 'decodeEntities'=>true, 'tl_class'=>'w50'),
            'load_callback'           => array(array('tl_short_urls', 'loadName')),
            'save_callback'           => array( array('tl_short_urls', 'validateName') ),
            'sql'                     => "varchar(255) NOT NULL default ''"
        ),
        'domain' => array
        (
            'label'                   => &$GLOBALS['TL_LANG']['tl_short_urls']['domain'],
            'exclude'                 => true,
            'inputType'               => 'select',
            'options_callback'        => array('tl_short_urls', 'getDomains'),
            'eval'                    => array('tl_class'=>'w50','submitOnChange'=>true),
            'save_callback'           => array( array('tl_short_urls', 'validateDomain') ),
            'sql'                     => "int(10) unsigned NOT NULL default '0'"
        ),
        'redirect' => array
        (
            'label'                   => &$GLOBALS['TL_LANG']['tl_short_urls']['redirect'],
            'default'                 => 'permanent',
            'exclude'                 => true,
            'inputType'               => 'select',
            'options'                 => array('permanent', 'temporary'/*, 'alias'*/),
            'reference'               => &$GLOBALS['TL_LANG']['tl_short_urls'],
            'eval'                    => array('tl_class' => 'w50'),
            'sql'                     => "varchar(32) NOT NULL default ''"
        ),
        'target' => array
        (
            'label'                   => &$GLOBALS['TL_LANG']['tl_short_urls']['target'],
            'exclude'                 => true,
            'eval'                    => array('rgxp'=>'url', 'decodeEntities'=>true, 'fieldType'=>'radio', 'filesOnly'=>true, 'tl_class'=>'w50 wizard', 'mandatory'=>true),
            'wizard'                  => array( array('tl_short_urls', 'pagePicker') ),
            'inputType'               => 'text',
            'sql'                     => "text NULL"
        ),
        'disable' => array
        (
            'label'                   => &$GLOBALS['TL_LANG']['tl_short_urls']['disable'],
            'exclude'                 => true,
            'filter'                  => true,
            'inputType'               => 'checkbox',
            'eval'                    => array('tl_class'=>'clr'),
            'sql'                     => "char(1) NOT NULL default ''"
        )
    )
);

if (interface_exists('Contao\CoreBundle\Picker\DcaPickerProviderInterface'))
{
    $GLOBALS['TL_DCA']['tl_short_urls']['fields']['target']['eval']['dcaPicker'] = true;
    unset($GLOBALS['TL_DCA']['tl_short_urls']['fields']['target']['eval']['fieldType']);
    unset($GLOBALS['TL_DCA']['tl_short_urls']['fields']['target']['eval']['filesOnly']);
    unset($GLOBALS['TL_DCA']['tl_short_urls']['fields']['target']['wizard']);
}

/**
 * Provide miscellaneous methods that are used by the data configuration array.
 *
 * @author Fritz Michael Gschwantner <https://github.com/fritzmg>
 */
class tl_short_urls extends Backend
{

    /**
     * Decodes the name
     * @param mixed         $varValue
     * @param DataContainer $dc
     * @return string
     */
    public function loadName($varValue, DataContainer $dc)
    {
        // check for query parameters
        if (strpos($varValue, '?') !== false)
        {
            $pos = strpos($varValue, '?');
            $url = substr($varValue, 0, $pos);
            $par = substr($varValue, $pos + 1);

            if ($par)
            {
                parse_str($par, $arrParameters);
                return rawurldecode($url) . '?' . http_build_query($arrParameters);
            }
        }

        return rawurldecode($varValue);
    }

    /**
     * Process and validate the name of a short url
     * @param mixed         $varValue
     * @param DataContainer $dc
     * @return string
     * @throws Exception
     */
    public function validateName($varValue, DataContainer $dc)
    {
        // check for query parameters
        if (strpos($varValue, '?') !== false)
        {
            $pos = strpos($varValue, '?');
            $url = substr($varValue, 0, $pos);
            $par = substr($varValue, $pos + 1);

            if ($par)
            {
                parse_str($par, $arrParameters);
                return implode('/', array_map('rawurlencode', explode('/', rawurldecode($url)))) . '?' . http_build_query($arrParameters);
            }
        }

        $varValue = rawurldecode($varValue);
        $this->validate($varValue, $dc->activeRecord->domain);
        return implode('/', array_map('rawurlencode', explode('/', $varValue)));
    }

    /**
     * Process and validate the name of a short url
     * @param mixed         $varValue
     * @param DataContainer $dc
     * @return string
     * @throws Exception
     */
    public function validateDomain($varValue, DataContainer $dc)
    {
        $this->validate($dc->activeRecord->name, $varValue);
        return $varValue;
    }

    /**
     * Validate the name of a short url
     * @param  string $name
     * @param  string $domain
     * @return void
     */
    private function validate($name, $domain)
    {
        // check if a page exists
        if( !\Config::get('urlSuffix') && ( $objPages = \PageModel::findByAlias( $name ) ) !== null )
        {
            // check if short url has a domain
            if( $domain > 0 )
            {
                $valid = true;
                while( $objPages->next() )
                {
                    // load page details
                    $objPages->current()->loadDetails();

                    // if one of the found pages is within the same domain, do not allow it
                    if( $objPages->rootId == $domain )
                    {
                        $valid = false;
                        break;
                    }
                }

                if( $valid )
                    return;
            }

            // otherwise throw exception
            throw new Exception(sprintf($GLOBALS['TL_LANG']['tl_short_urls']['pageExists'], $name));
        }
    }

    /**
     * Generate the page picker link
     *
     * @param DataContainer $dc
     *
     * @return string
     */
    public function pagePicker(DataContainer $dc)
    {
        return ' table . '&field=' . $dc->field . '&value=' . str_replace(array('{{link_url::', '}}'), '', $dc->value) . '&switch=1' . '" title="' . specialchars($GLOBALS['TL_LANG']['MSC']['pagepicker']) . '" onclick="Backend.getScrollOffset();Backend.openModalSelector({\'width\':768,\'title\':\'' . specialchars(str_replace("'", "\\'", $GLOBALS['TL_LANG']['MOD']['page'][0])) . '\',\'url\':this.href,\'id\':\'' . $dc->field . '\',\'tag\':\'ctrl_'. $dc->field . ((Input::get('act') == 'editAll') ? '_' . $dc->id : '') . '\',\'self\':this});return false">' . Image::getHtml('pickpage.gif', $GLOBALS['TL_LANG']['MSC']['pagepicker'], 'style="vertical-align:top;cursor:pointer"') . '';
    }

    /**
     * Return the "toggle visibility" button
     *
     * @param array  $row
     * @param string $href
     * @param string $label
     * @param string $title
     * @param string $icon
     * @param string $attributes
     *
     * @return string
     */
    public function toggleIcon($row, $href, $label, $title, $icon, $attributes)
    {
        if (strlen(Input::get('tid')))
        {
            $this->toggleVisibility(Input::get('tid'), (Input::get('state') == 1), (@func_get_arg(12) ?: null));
            $this->redirect($this->getReferer());
        }

        $href .= '&tid='.$row['id'].'&state='.$row['disable'];

        if ($row['disable'])
        {
            $icon = 'invisible.gif';
        }

        return 'addToUrl($href).'" title="'.specialchars($title).'"'.$attributes.'>'.Image::getHtml($icon, $label, 'data-state="' . ($row['disable'] ? 0 : 1) . '"').' ';
    }

    /**
     * Disable/enable a short url
     *
     * @param integer       $intId
     * @param boolean       $blnVisible
     * @param DataContainer $dc
     */
    public function toggleVisibility($intId, $blnVisible, DataContainer $dc=null)
    {
        Input::setGet('id', $intId);
        Input::setGet('act', 'toggle');

        // Trigger the save_callback
        if (is_array($GLOBALS['TL_DCA']['tl_short_urls']['fields']['disable']['save_callback']))
        {
            foreach ($GLOBALS['TL_DCA']['tl_short_urls']['fields']['disable']['save_callback'] as $callback)
            {
                if (is_array($callback))
                {
                    $this->import($callback[0]);
                    $blnVisible = $this->$callback[0]->$callback[1]($blnVisible, ($dc ?: $this));
                }
                elseif (is_callable($callback))
                {
                    $blnVisible = $callback($blnVisible, ($dc ?: $this));
                }
            }
        }

        // Update the database
        $this->Database->prepare("UPDATE tl_short_urls SET tstamp=". time() .", disable='" . ($blnVisible ? '' : 1) . "' WHERE id=?")
                       ->execute($intId);
    }

    /**
     * Display list info in the backend
     *
     * @param array $arrRow
     *
     * @return string
     */
    public function labelCallback($arrRow)
    {
        // get the target URL
        $targetURL = \ShortURLs::processTarget( $arrRow['target'] );

        // remove current host
        $targetURL = str_replace( \Environment::get('base'), '', $targetURL );

        // check for domain restriction
        $domain = '';
        if( $arrRow['domain'] && ( $objPage = \PageModel::findById( $arrRow['domain'] ) ) !== null )
            $domain = $objPage->dns . '/';

        // generate list record
        return '
[' . ( $arrRow['redirect'] == 'permanent' ? 301 : 302 ) . ']

' . $domain . rawurldecode($arrRow['name']) . ' » ' . rawurldecode($targetURL) . '
';
    }

    /**
     * Retreives all available domains in this installation (plus empty selection)
     *
     * @return array
     */
    public function getDomains()
    {
        // options array
        $options = array( '' => $GLOBALS['TL_LANG']['tl_short_urls']['noDomain'] );

        // get the root pages and their dns settings
        if( version_compare( VERSION, '3.5', '>=' ) )
        {
            if( ( $objPages = \PageModel::findPublishedRootPages() ) !== null )
                while( $objPages->next() )
                    if( $objPages->dns )
                        $options[$objPages->id] = $objPages->dns;
        }
        else
        {
            $t = \PageModel::getTable();
            $arrColumns = array("$t.type=?");

            if (!BE_USER_LOGGED_IN)
            {
                $time = time();
                $arrColumns[] = "($t.start='' OR $t.start<='$time') AND ($t.stop='' OR $t.stop>'" . ($time + 60) . "') AND $t.published='1'";
            }

            $objPages = \PageModel::findBy($arrColumns, 'root');

            if( $objPages !== null )
                while( $objPages->next() )
                    if( $objPages->dns )
                        $options[$objPages->id] = $objPages->dns;
        }

        // return the options
        return $options;
    }
}


<?php

/**
 * This file is part of the m1\vars library
 *
 * (c) m1 <hello@milescroxford.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @package     m1/vars
 * @version     1.1.0
 * @author      Miles Croxford <hello@milescroxford.com>
 * @copyright   Copyright (c) Miles Croxford <hello@milRescroxford.com>
 * @license     http://github.com/m1/vars/blob/master/LICENSE
 * @link        http://github.com/m1/vars/blob/master/README.MD Documentation
 */

namespace M1\Vars\Loader;

use Yosymfony\Toml\Toml;

/**
 * The Toml file loader
 *
 * @since 0.1.0
 */
class TomlLoader extends AbstractLoader
{
    /**
     * {@inheritdoc}
     */
    public static $supported = array('toml', 'tml');

    /**
     * {@inheritdoc}
     *
     * @throws \RuntimeException If the `yosymfonytoml` library is not installed or the toml file is not valid
     */
    public function load()
    {
        try {
            $this->content = Toml::parse($this->entity);
        } catch (\Exception $e) {
            throw new \RuntimeException(
                sprintf("'%s' failed to load with the error '%s'", $this->entity, $e)
            );
        }
        return $this;
    }
}


<?php
/**
 * Lord of the Files - Admin hooks.
 *
 * This covers integrations and behavioral overrides.
 *
 * @package blob-mimes
 * @author  Blobfolio, LLC <hello@blobfolio.com>
 */

namespace blobfolio\wp\bm;

class admin {

	// The WP.org API URL for getting contributor information.
	const PLUGIN_API = 'https://api.wordpress.org/plugins/info/1.0/%s.json?fields[contributors]=1&fields[sections]=0&fields[downloaded]=0&fields[downloadlink]=0&fields[last_updated]=0&fields[tags]=0&fields[tested]=0&fields[homepage]=0&fields[rating]=0&fields[ratings]=0&fields[requires]=0';

	protected static $version;
	protected static $remote_version;
	protected static $remote_home;

	// -----------------------------------------------------------------
	// Init
	// -----------------------------------------------------------------

	/**
	 * Register Actions and Filters
	 *
	 * @return void Nothing.
	 */
	public static function init() {
		$class = get_called_class();

		// Admin menu entry for upload debugging.
		add_action('admin_menu', array($class, 'menu_debug'));

		// Override upload file validation (general).
		add_filter('wp_check_filetype_and_ext', array($class, 'check_filetype_and_ext'), 10, 4);

		// Override upload file validation (SVG).
		add_filter('wp_check_filetype_and_ext', array($class, 'check_filetype_and_ext_svg'), 15, 4);

		// Set up translations.
		add_action('plugins_loaded', array($class, 'localize'));

		// Update check on debug page.
		add_action('admin_notices', array($class, 'debug_notice'));

		// AJAX hook for disabling contributor lookups.
		add_action('wp_ajax_bm_ajax_disable_contributor_notice', array($class, 'disable_contributor_notice'));

		// And the corresponding warnings.
		add_filter('admin_notices', array($class, 'contributors_changed_notice'));

		// Pull remote contributor information.
		$next = wp_next_scheduled('cron_get_remote_contributors');
		if ('disabled' === get_option('bm_contributor_notice', false)) {
			// Make sure the CRON job is disabled.
			if ($next) {
				wp_unschedule_event($next, 'cron_get_remote_contributors');
			}
		}
		else {
			add_action('cron_get_remote_contributors', array($class, 'cron_get_remote_contributors'));
			if (!$next) {
				wp_schedule_event(time(), 'hourly', 'cron_get_remote_contributors');
			}
		}

		// Register plugins page quick links if we aren't running in
		// Must-Use mode.
		if (!BLOBMIMES_MUST_USE) {
			add_filter('plugin_action_links_' . plugin_basename(BLOBMIMES_INDEX), array($class, 'plugin_action_links'));
		}
	}

	/**
	 * Localize
	 *
	 * @return void Nothing.
	 */
	public static function localize() {
		if (BLOBMIMES_MUST_USE) {
			load_muplugin_textdomain('blob-mimes', basename(BLOBMIMES_BASE_PATH) . '/languages');
		}
		else {
			load_plugin_textdomain('blob-mimes', false, basename(BLOBMIMES_BASE_PATH) . '/languages');
		}
	}

	/**
	 * Plugin Action Links
	 *
	 * Add a few links to the plugins page so people can more easily
	 * find what they're looking for.
	 *
	 * @param array $links Links.
	 * @return array Links.
	 */
	public static function plugin_action_links($links) {
		if (current_user_can('manage_options')) {
			$links[] = '' . __('Debug File Validation', 'blob-mimes') . '';
		}

		$links[] = '' . esc_html__('Documentation', 'blob-mimes') . '';

		return $links;
	}

	// ----------------------------------------------------------------- end init

	// -----------------------------------------------------------------
	// Upload Debugger
	// -----------------------------------------------------------------

	/**
	 * Menu: Upload Debugger
	 *
	 * @return void Nothing.
	 */
	public static function menu_debug() {
		add_submenu_page(
			'tools.php',
			__('Debug File Validation', 'blob-mimes'),
			__('Debug File Validation', 'blob-mimes'),
			'manage_options',
			'blob-mimes-admin',
			array(get_called_class(), 'page_debug')
		);
	}

	/**
	 * Page: Upload Debugger
	 *
	 * @return void Nothing.
	 */
	public static function page_debug() {
		if (!static::has_update()) {
			require(BLOBMIMES_BASE_PATH . 'admin/debug.php');
		}
		else {
			echo '

' . esc_html__('Debug File Validation', 'blob-mimes') . '

';
		}
	}

	// ----------------------------------------------------------------- end upload debugger

	// -----------------------------------------------------------------
	// Update Checking (MU)
	// -----------------------------------------------------------------

	/**
	 * Get Plugin Version
	 *
	 * @return string Version.
	 */
	public static function get_version() {
		if (is_null(static::$version)) {
			$plugin_data = get_plugin_data(BLOBMIMES_INDEX, false, false);
			if (isset($plugin_data['Version'])) {
				static::$version = $plugin_data['Version'];
			}
			else {
				static::$version = '0.0';
			}
		}

		return static::$version;
	}

	/**
	 * Get Remote Version
	 *
	 * @return string Version.
	 */
	public static function get_remote_version() {
		if (is_null(static::$remote_version)) {
			$response = plugins_api(
				'plugin_information',
				array('slug'=>'blob-mimes')
			);
			if (
				!is_wp_error($response) &&
				is_a($response, 'stdClass') &&
				isset($response->version)
			) {
				static::$remote_version = $response->version;
				static::$remote_home = $response->homepage;
			}
			else {
				static::$remote_version = '0.0';
			}
		}

		return static::$remote_version;
	}

	/**
	 * Debug Notice
	 *
	 * This adds a notice to the file debug page, either informing users
	 * that they need to update the plugin, or explaining the page's
	 * purpose.
	 *
	 * @return void Nothing.
	 */
	public static function debug_notice() {
		// We only want to generate a notice if someone is viewing the
		// file debug page, and then only if they didn't just upload a
		// file.
		$screen = get_current_screen();
		if (
			('POST' !== getenv('REQUEST_METHOD')) &&
			('tools_page_blob-mimes-admin' === $screen->id)
		) {
			// Update notice takes priority.
			if (static::has_update()) {
				if (BLOBMIMES_MUST_USE) {
					$update_link = '' . esc_html__('update', 'blob-mimes') . '';
				}
				else {
					$update_link = '' . esc_html__('update', 'blob-mimes') . '';
				}

				// Update warning.
				$notice_type = 'warning';
				$notice = sprintf(
					esc_html__('Please %s %s to the latest release (%s) before debugging upload-related issues.', 'blob-mimes'),
					$update_link,
					'Lord of the Files',
					'' . static::get_remote_version() . ''
				);

			}
			// Otherwise explain the page's purpose.
			else {
				$notice_type = 'info';
				$notice = esc_html__('If a file has been rejected from the Media Library for "security reasons", use the form below to find out more information.', 'blob-mimes');
			}

			echo '

' . $notice . '

';
		}
	}

	/**
	 * Has Update?
	 *
	 * We need to query the API because WordPress won't check for
	 * updates if this plugin is installed as Must-Use.
	 *
	 * @return bool True/false.
	 */
	public static function has_update() {
		require_once(trailingslashit(ABSPATH) . 'wp-admin/includes/plugin.php');
		require_once(trailingslashit(ABSPATH) . 'wp-admin/includes/plugin-install.php');
		return (version_compare(static::get_version(), static::get_remote_version()) < 0);
	}

	// ----------------------------------------------------------------- end self updates

	// -----------------------------------------------------------------
	// File Validation
	// -----------------------------------------------------------------

	/**
	 * Override Upload File Validation
	 *
	 * This hooks into wp_check_filetype_and_ext() to improve its
	 * determinations.
	 *
	 * @see wp_check_filetype_and_ext()
	 *
	 * @param array $checked Previous check status.
	 * @param string $file File path.
	 * @param string $filename File name.
	 * @param array $mimes Mimes.
	 * @return array Checked status.
	 */
	public static function check_filetype_and_ext($checked, $file, $filename, $mimes) {
		// We don't care what WP has already done.
		$proper_filename = false;

		// Do basic extension validation and MIME mapping.
		$wp_filetype = mime::check_real_filetype($file, $filename, $mimes);
		$ext = $wp_filetype['ext'];
		$type = $wp_filetype['type'];

		// We can't do any further validation without a file to work with.
		if (!@file_exists($file)) {
			return compact('ext', 'type', 'proper_filename');
		}

		// If the type is valid, should we be renaming the file?
		if (false !== $ext && false !== $type) {
			$new_filename = mime::update_filename_extension($filename, $ext);
			if ($filename !== $new_filename) {
				$proper_filename = $new_filename;
			}
		}

		return compact('ext', 'type', 'proper_filename');
	}

	/**
	 * Sanitize SVG Uploads
	 *
	 * This is triggered after our general content-based fixer, so if
	 * something is claiming to be an SVG here, it should actually be
	 * one.
	 *
	 * @see wp_check_filetype_and_ext()
	 *
	 * @param array $checked Previous check status.
	 * @param string $file File path.
	 * @param string $filename File name.
	 * @param array $mimes Mimes.
	 * @return array Checked status.
	 */
	public static function check_filetype_and_ext_svg($checked, $file, $filename, $mimes) {
		// Only need to do something if the type is SVG.
		if ('image/svg+xml' === $checked['type']) {
			try {
				$contents = @file_get_contents($file);
				$contents = svg::sanitize($contents);

				// Overwrite the contents if we're good.
				if (is_string($contents) && $contents) {
					@file_put_contents($file, $contents);

					// In case it got renamed somewhere along the way.
					if ($checked['proper_filename']) {
						$checked['proper_filename'] = mime::update_filename_extension($checked['proper_filename'], '.svg');
					}
				}
				// Otherwise just fail the download.
				else {
					$checked['type'] = $checked['ext'] = false;
				}
			} catch (\Throwable $e) {
				error_log($e->getMessage());
				$checked['type'] = $checked['ext'] = false;
			} catch (\Exception $e) {
				$checked['type'] = $checked['ext'] = false;
			}
		}

		return $checked;
	}

	// ----------------------------------------------------------------- end files

	// -----------------------------------------------------------------
	// Plugin Contributor Monitoring
	// -----------------------------------------------------------------

	/**
	 * Local Contributors
	 *
	 * Try to find and parse installed plugins' readme.txt files for
	 * contributor information.
	 *
	 * @param string $slug Plugin slug (index version).
	 * @param string $version Version.
	 * @return array Contributors.
	 */
	protected static function get_local_contributors($slug, $version) {
		// Obviously bad data.
		if (!$slug || !$version || !is_string($slug) || !is_string($version)) {
			return array();
		}

		// Cache this for each plugin/version pair.
		$transient_key = 'bm_local_contrib_' . md5("$slug|$version");
		if (false !== ($contributors = get_transient($transient_key))) {
			return $contributors;
		}

		// The directory in question.
		$dir = trailingslashit(WP_PLUGIN_DIR) . $slug;
		if (!@is_dir($dir)) {
			return array();
		}

		// If the readme file isn't named normally, we have to run
		// through the directory (but just one level).
		if (
			!@is_file("$dir/readme.txt") ||
			(false === ($contributors = static::parse_readme_contributors($slug)))
		) {
			$files = list_files($dir, 1);
			if (is_array($files)) {
				foreach ($files as $file) {
					if (
						preg_match('/^readme\.(txt|md)$/i', basename($file)) &&
						@is_file($file) &&
						(false !== ($contributors = static::parse_readme_contributors($file)))
					) {
						break;
					}
				}
			}
		}

		// Looking good!
		if (is_array($contributors) && count($contributors)) {
			set_transient($transient_key, $contributors, WEEK_IN_SECONDS);
		}
		// We should still cache a negative lookup because if we
		// couldn't get the info this time, we probably won't next time.
		else {
			set_transient($transient_key, array(), WEEK_IN_SECONDS);
		}

		return $contributors;
	}

	/**
	 * Parse Readme Contributors
	 *
	 * @param string $file File.
	 * @return array|bool Contributors or false.
	 */
	protected static function parse_readme_contributors($file) {
		if (!$file || !@is_file($file)) {
			return false;
		}

		require_once (ABSPATH . 'wp-admin/includes/file.php');

		// This can actually be parsed the same way plugin index files
		// are. Neat!
		$headers = get_file_data($file, array('Contributors'=>'Contributors'));
		if (isset($headers['Contributors'])) {
			// These are comma-separated, so let's array-ize them and
			// clean it up a bit.
			$headers['Contributors'] = explode(',', $headers['Contributors']);
			foreach ($headers['Contributors'] as $k=>$v) {
				$headers['contributors'][$k] = trim($v);
				if (!$v) {
					unset($headers['contributors'][$k]);
				}
			}

			// We should have at least one!
			if (count($headers['contributors'])) {
				$headers['contributors'] = array_unique($headers['contributors']);
				sort($headers['contributors']);
				return $headers['contributors'];
			}
		}

		// No go.
		return false;
	}

	/**
	 * Contributor Change Notice
	 *
	 * Warn users if a pending plugin update has different contributors
	 * than the version they currently have installed.
	 *
	 * @see https://core.trac.wordpress.org/ticket/42255
	 * @see https://meta.trac.wordpress.org/ticket/3207
	 *
	 * @return void Nothing.
	 */
	public static function contributors_changed_notice() {
		// Do not display if disabled.
		if ('disabled' === get_option('bm_contributor_notice', false)) {
			return;
		}

		// We only want to display this on the plugins and update pages.
		$screen = get_current_screen();
		if (('update-core' !== $screen->id) && ('plugins' !== $screen->id)) {
			return;
		}

		// Make sure we've run remote checks before.
		if (
			(false === ($remote = get_option('bm_remote_contributors', false))) ||
			!count($remote)
		) {
			return;
		}

		// Make sure updates are checked.
		wp_update_plugins();
		$updates = get_site_transient('update_plugins');
		if (
			!isset($updates->response, $updates->checked) ||
			!is_array($updates->response) ||
			!count($updates->response)
		) {
			return;
		}

		// Look for any local/remote changes.
		$warnings = array();
		foreach ($updates->response as $k=>$v) {
			$slug = $v->slug;

			// Skip this plugin if there were no updates or remote data.
			if (!isset($updates->checked[$k], $remote[$slug])) {
				continue;
			}

			// Pull the local contributor data.
			$version = $updates->checked[$k];
			$local = static::get_local_contributors($slug, $version);
			if (!count($local)) {
				continue;
			}

			// Note the differences.
			$added = array_diff($remote[$slug], $local);
			$removed = array_diff($local, $remote[$slug]);
			if (count($added) || count($removed)) {
				$warnings[$slug] = array(
					'new'=>$added,
					'removed'=>$removed,
				);
			}
		}

		// Nothing to do?
		if (!count($warnings)) {
			return;
		}

		// Let's build some HTML for the output.
		foreach ($warnings as $k=>$v) {
			$tmp = array();

			foreach ($v['removed'] as $v2) {
				$tmp[] = ' ' . esc_html($v2) . '';
			}

			foreach ($v['new'] as $v2) {
				$tmp[] = ' ' . esc_html($v2) . '';
			}

			$warnings[$k] = '

				' . esc_html($k) . ':
				' . implode('     ', $tmp) . '

';
		}

		// Print the notice!
		?>

			<?php
			echo '
' . sprintf(
				esc_html__('%s The list of contributors for one or more plugins has changed since you last performed updates.', 'blob-mimes'),
				'' . esc_html__('Warning', 'blob-mimes') . ':'
			) . '
';

			echo '

' . implode('', $warnings) . '

';

			echo '
' . sprintf(
				esc_html__('If you prefer not to receive these notices in the future, click %s.', 'blob-mimes'),
				'' . esc_html__('here', 'blob-mimes') . ''
			) . '
';
			?>

		<?php
	}

	/**
	 * AJAX: Disable Contributor Notice
	 *
	 * Some people may not find the contributor change notices helpful.
	 * This will prevent them from being calculated or displaying.
	 *
	 * @see https://core.trac.wordpress.org/ticket/42255
	 * @see https://meta.trac.wordpress.org/ticket/3207
	 *
	 * @return void Nothing.
	 */
	public static function disable_contributor_notice() {
		if (
			current_user_can('manage_options') &&
			isset($_POST['n']) &&
			wp_verify_nonce($_POST['n'], 'bm_ajax_disable_contributor_notice')
		) {
			update_option('bm_contributor_notice', 'disabled');
			echo 1;
		}
		else {
			echo 0;
		}

		exit;
	}

	/**
	 * Extract Plugin Slug From Path
	 *
	 * @param string $path Path.
	 * @return string|bool Slug or false.
	 */
	public static function get_plugin_slug_by_path($path) {
		if (is_string($path) && $path) {
			$slug = $path;
			while (false !== strpos($slug, '/')) {
				$slug = dirname($slug);
			}
			$slug = preg_replace('/\.php$/i', '', $slug);

			return $slug ? $slug : false;
		}

		return false;
	}

	/**
	 * Pull Plugin Contributors
	 *
	 * This is a simple CRON job that will pull contributor information
	 * for any installed plugins so we have that data for later
	 * reference.
	 *
	 * @return void Nothing.
	 */
	public static function cron_get_remote_contributors() {
		require_once(ABSPATH . 'wp-admin/includes/plugin.php');
		$plugins = get_plugins();
		$out = array();

		// We'll also keep track of plugins that aren't hosted on
		// WP.org. This might change, so we'll periodically rebuild the
		// list.
		$save_invalid = false;
		if (false === ($invalid = get_transient('bm_external_plugins'))) {
			$save_invalid = true;
			$invalid = array();
		}

		foreach ($plugins as $k=>$v) {
			// Can't check it.
			if (
				false === ($slug = static::get_plugin_slug_by_path($k)) ||
				isset($invalid[$slug])
			) {
				continue;
			}

			// The plugins_api() function does not fully support the
			// API spec, so we have to do this manually.
			$url = sprintf(static::PLUGIN_API, $slug);
			$response = wp_remote_get(
				$url,
				array(
					'timeout'=>3,
				)
			);
			if (200 === wp_remote_retrieve_response_code($response)) {
				$body = wp_remote_retrieve_body($response);
				$body = json_decode($body, true);
				if ($body) {
					// If WP returned an error, note the slug so we can
					// avoid checking it in the future.
					if (isset($body['error'])) {
						$invalid[$slug] = true;
						$save_invalid = true;
						continue;
					}

					if (
						isset($body['contributors']) &&
						is_array($body['contributors']) &&
						count($body['contributors'])
					) {
						ksort($body['contributors']);
						$out[$slug] = array_keys($body['contributors']);
					}
				}
			}
		}

		// Save the contributors.
		update_option('bm_remote_contributors', $out);

		// Save the invalid plugins.
		if ($save_invalid) {
			set_transient('bm_external_plugins', $invalid, DAY_IN_SECONDS);
		}
	}

	// ----------------------------------------------------------------- end contributors
}


<?php

/**
 * This file is part of the m1\vars library
 *
 * (c) m1 <hello@milescroxford.com>
 *
 * For the full copyright and license information, please view the LICENSE
 * file that was distributed with this source code.
 *
 * @package     m1/vars
 * @version     1.1.0
 * @author      Miles Croxford <hello@milescroxford.com>
 * @copyright   Copyright (c) Miles Croxford <hello@milescroxford.com>
 * @license     http://github.com/m1/vars/blob/master/LICENSE
 * @link        http://github.com/m1/vars/blob/master/README.MD Documentation
 */

namespace M1\Vars\Loader;

/**
 * The ini file loader
 *
 * @since 0.1.0
 */
class IniLoader extends AbstractLoader
{
    /**
     * {@inheritdoc}
     */
    public static $supported = array('ini');

    /**
     * {@inheritdoc}
     */
    public function load()
    {
        try {
            $this->content = parse_ini_file($this->entity, true);
        } catch (\Exception $e) {
            throw new \RuntimeException(sprintf(
                "%s threw an exception: %s",
                $this->entity,
                $e
            ));
        }
        return $this;
    }
}