diff --git a/.gitignore b/.gitignore index 825c1913..c3820b63 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ vendor/ .php_cs.cache composer.lock phpunit.xml + #phpstorm config folder +.idea/ \ No newline at end of file diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 7c4f9a0e..ef4da504 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -1,9 +1,9 @@ diff --git a/src/Exception/MailboxesParserException.php b/src/Exception/MailboxesParserException.php new file mode 100644 index 00000000..141c244a --- /dev/null +++ b/src/Exception/MailboxesParserException.php @@ -0,0 +1,9 @@ + ['inbox'], + self::SENT => ['sent', 'sent messages', 'INBOX.Sent', '[Gmail]/Sent Mail'], + self::DRAFT => ['drafts', 'INBOX.Drafts', '[Gmail]/Drafts'], + self::SPAM => ['spam', 'INBOX.spam', '[Gmail]/spam'], + self::TRASH => ['trash', 'bin', 'INBOX.trash', '[Gmail]/trash'], + self::TEMPLATES => ['templates'], + self::ARCHIVES => ['archives'], + ]; + + private $specialFoldersNames = [ + self::DRAFT => 'Drafts', + self::INBOX => 'Inbox', + self::SENT => 'Sent', + self::SPAM => 'Spam', + self::TRASH => 'Trash', + self::TEMPLATES => 'Templates', + self::ARCHIVES => 'Archives', + ]; + + private $specialFoldersOrder = [ + self::INBOX => 1, + self::SENT => 2, + self::DRAFT => 3, + self::TEMPLATES => 4, + self::ARCHIVES => 10000, + self::SPAM => 20000, + self::TRASH => 30000, + ]; + + /** + * MailboxesTree constructor. + * + * @param MailboxInterface[] $mailboxes + */ + public function __construct($mailboxes) + { + $this->mailboxes = $mailboxes; + } + + /** + * Set language for parser. + * + * @param string $lang + */ + public function setLanguage(string $lang) + { + $path = __DIR__ . DIRECTORY_SEPARATOR . 'languages' . DIRECTORY_SEPARATOR . $lang . DIRECTORY_SEPARATOR . 'names.php'; + if (!\is_file($path)) { + throw new MailboxesParserException(\sprintf('File for language names %s does not exist', $path)); + } + $names = require $path; + $this->setSpecialFoldersNames($names); + + $path = __DIR__ . DIRECTORY_SEPARATOR . 'languages' . DIRECTORY_SEPARATOR . $lang . DIRECTORY_SEPARATOR . 'ids.php'; + if (!\is_file($path)) { + throw new MailboxesParserException(\sprintf('File for language ids %s does not exist', $path)); + } + $ids = require $path; + foreach ($ids as $specialFolder => $idsArray) { + foreach ($idsArray as $id) { + $this->addSpecialFolderId($specialFolder, $id); + } + } + } + + /** + * @return ParsedMailbox[] + */ + private function parse(): array + { + $this->folders = []; + \usort($this->mailboxes, [$this, 'sortByMailboxName']); + foreach ($this->mailboxes as $k => $mailbox) { + $mailboxName = $mailbox->getName(); + $folder = new ParsedMailbox(); + $folder->setMailbox($mailbox); + $folder->setMailboxName($mailboxName); + $special = $this->getSpecialFolder($mailboxName); + $folder->setSpecial($special); + $folder->setName($special ? $this->specialFoldersNames[$special] : $this->getName($mailboxName, $mailbox->getDelimiter())); + $folder->setOrder($special ? $this->specialFoldersOrder[$special] : $this->getOrder($mailboxName, $mailbox->getDelimiter())); + $folder->setLevel($this->getFolderLevel($mailboxName, $mailbox->getDelimiter())); + $folder->setDelimiter($mailbox->getDelimiter()); + $this->folders[] = $folder; + } + + \usort($this->folders, [$this, 'sortByOrder']); + + return $this->folders; + } + + /** + * @param MailboxInterface $a + * @param MailboxInterface $b + * + * @return int + */ + private function sortByMailboxName(MailboxInterface $a, MailboxInterface $b): int + { + return $a->getName() <=> $b->getName(); + } + + /** + * @param ParsedMailbox $a + * @param ParsedMailbox $b + * + * @return int + */ + private function sortByOrder(ParsedMailbox $a, ParsedMailbox $b): int + { + return $a->getOrder() <=> $b->getOrder(); + } + + /** + * @return ParsedMailbox[] + */ + public function getFolders(): array + { + if (!$this->folders) { + $this->parse(); + } + + return $this->folders; + } + + /** + * @return array + */ + public function getTreeStructure(): array + { + if (!$this->treeStructure) { + $treeParser = new MailboxesTreeParser(); + $this->treeStructure = $treeParser->parse($this->getFolders()); + } + + return $this->treeStructure; + } + + /** + * @param array $specialFoldersNames + */ + public function setSpecialFoldersNames(array $specialFoldersNames) + { + $this->specialFoldersNames = $specialFoldersNames; + } + + /** + * @param $mailboxName + * + * @return null|int|string + */ + private function getSpecialFolder($mailboxName) + { + foreach ($this->specialFoldersIds as $specialFolderKind => $names) { + $lower = \mb_strtolower($mailboxName); + foreach ($names as $name) { + if ($lower === \mb_strtolower($name)) { + return $specialFolderKind; + } + } + } + + return; + } + + /** + * @param $mailboxName + * @param string $delimiter + * + * @return string + */ + private function getName($mailboxName, $delimiter = '.'): string + { + $e = \explode($delimiter, $mailboxName); + + return \ucfirst($e[\count($e) - 1]); + } + + /** + * @param $mailboxName + * @param $delimiter + * + * @return float|int|mixed + */ + private function getOrder($mailboxName, $delimiter) + { + if ($this->folders[$mailboxName]['special']) { + return $this->specialFoldersOrder[$this->folders[$mailboxName]['special']]; + } + $e = \explode($delimiter, $mailboxName); + + $level = \count($e) - 1; + if ('INBOX' === $e[0] && 1 === $level) { + $level = -1; + } + if ($e[1]) { + \array_pop($e); + $parentMailboxName = \implode($delimiter, $e); + if ($level === -1) { + $multiplier = 100; + } else { + $power = -1 * ($level * 2); + $multiplier = 10 ** ($power); + } + $parsedParent = null; + /** @var ParsedMailbox $parsedMailbox */ + foreach ($this->folders as $parsedMailbox) { + if ($parsedMailbox->getMailboxName() === $parentMailboxName) { + $parsedParent = $parsedMailbox; + + break; + } + } + if ($parsedParent) { + $parsedParent->setSubfolders($parsedParent->getSubfolders() + 1); + $order = ($parsedParent->getOrder() + ($parsedParent->getSubfolders() * $multiplier)); + } else { + $order = 10; + } + } else { + $order = 10; + } + + return $order; + } + + /** + * @param $special + * + * @return null|string + */ + public function getMailboxNameForSpecial($special) + { + /** @var ParsedMailbox $parsedMailbox */ + foreach ($this->folders as $parsedMailbox) { + if ($parsedMailbox->getSpecial() === $special) { + return $parsedMailbox->getMailboxName(); + } + } + + return; + } + + /** + * @param string $specialFolder Name of special folder + * @param string $id Id for that special folder + */ + public function addSpecialFolderId($specialFolder, $id) + { + $this->specialFoldersIds[$specialFolder][] = $id; + } + + /** + * @param $mailboxName + * @param $delimiter + * + * @return int + */ + private function getFolderLevel($mailboxName, $delimiter): int + { + $e = \explode($delimiter, $mailboxName); + + return \count($e); + } +} diff --git a/src/MailboxesParser/MailboxesParserInterface.php b/src/MailboxesParser/MailboxesParserInterface.php new file mode 100644 index 00000000..f9e22fd4 --- /dev/null +++ b/src/MailboxesParser/MailboxesParserInterface.php @@ -0,0 +1,52 @@ +getDelimiter(), $value->getMailboxName()); + $newkey = []; + foreach ($k as $segment) { + $newkey[] = $segment; + $newkey[] = 'subfolders'; + } + + return \implode('.', $newkey); + }, + \array_keys($input), + $input + ); + + $arrayToParse = []; + foreach ($newKeys as $index => $value) { + $k = \explode('.', $value); + $keyWithoutLast = \implode('.', \array_splice($k, 0, -1)); + $arrayToParse[$value] = []; + if ($input[$index]) { + /** @var ParsedMailbox $parsedMailbox */ + $parsedMailbox = $input[$index]; + $arrayToParse[$keyWithoutLast . '.mailboxName'] = $parsedMailbox->getMailboxName(); + $arrayToParse[$keyWithoutLast . '.name'] = $parsedMailbox->getName(); + } + } + + $res = []; + \array_walk($arrayToParse, function ($value, $key) use (&$res) { + $this->set($res, $key, $value); + }); + + return $res; + } + + /** + * @param $array + * @param $key + * @param $value + * + * @return mixed + */ + private function set(&$array, $key, $value) + { + if (null === $key) { + return $array = $value; + } + $keys = \explode('.', $key); + while (\count($keys) > 1) { + $key = \array_shift($keys); + // If the key doesn't exist at this depth, we will just create an empty array + // to hold the next value, allowing us to create the arrays to hold final + // values at the correct depth. Then we'll keep digging into the array. + if (!isset($array[$key]) || !\is_array($array[$key])) { + $array[$key] = []; + } + $array = &$array[$key]; + } + $array[\array_shift($keys)] = $value; + + return $array; + } +} diff --git a/src/MailboxesParser/MailboxesTreeParserInterface.php b/src/MailboxesParser/MailboxesTreeParserInterface.php new file mode 100644 index 00000000..2bff983b --- /dev/null +++ b/src/MailboxesParser/MailboxesTreeParserInterface.php @@ -0,0 +1,24 @@ +mailbox; + } + + /** + * @param MailboxInterface $mailbox + */ + public function setMailbox(MailboxInterface $mailbox) + { + $this->mailbox = $mailbox; + } + + /** + * @return mixed + */ + public function getOrder() + { + return $this->order; + } + + /** + * @param mixed $order + */ + public function setOrder($order) + { + $this->order = $order; + } + + /** + * @return mixed + */ + public function getMailboxName() + { + return $this->mailboxName; + } + + /** + * @param mixed $mailboxName + */ + public function setMailboxName($mailboxName) + { + $this->mailboxName = $mailboxName; + } + + /** + * @return mixed + */ + public function getName() + { + return $this->name; + } + + /** + * @param mixed $name + */ + public function setName($name) + { + $this->name = $name; + } + + /** + * @return mixed + */ + public function getSpecial() + { + return $this->special; + } + + /** + * @param mixed $special + */ + public function setSpecial($special) + { + $this->special = $special; + } + + /** + * @return string + */ + public function getDelimiter(): string + { + return $this->delimiter; + } + + /** + * @param string $delimiter + */ + public function setDelimiter(string $delimiter) + { + $this->delimiter = $delimiter; + } + + /** + * @return int + */ + public function getLevel(): int + { + return $this->level; + } + + /** + * @param int $level + */ + public function setLevel(int $level) + { + $this->level = $level; + } + + /** + * @return int + */ + public function getSubfolders(): int + { + return $this->subfolders; + } + + /** + * @param int $subfolders + */ + public function setSubfolders(int $subfolders) + { + $this->subfolders = $subfolders; + } +} diff --git a/src/MailboxesParser/languages/pl/ids.php b/src/MailboxesParser/languages/pl/ids.php new file mode 100644 index 00000000..b7544413 --- /dev/null +++ b/src/MailboxesParser/languages/pl/ids.php @@ -0,0 +1,22 @@ + ['INBOX.odebrane', 'odebrane'], + MailboxesParser::SENT => ['INBOX.Wysłane', 'elementy wysłane', 'wysłane'], + MailboxesParser::DRAFT => ['szkice'], + MailboxesParser::SPAM => [], + MailboxesParser::TRASH => ['kosz'], + MailboxesParser::TEMPLATES => ['szablony'], + MailboxesParser::ARCHIVES => ['archiwum'], +]; diff --git a/src/MailboxesParser/languages/pl/names.php b/src/MailboxesParser/languages/pl/names.php new file mode 100644 index 00000000..bc9d7468 --- /dev/null +++ b/src/MailboxesParser/languages/pl/names.php @@ -0,0 +1,22 @@ + 'Odebrane', + MailboxesParser::SENT => 'Wysłane', + MailboxesParser::DRAFT => 'Szkice', + MailboxesParser::SPAM => 'Spam', + MailboxesParser::TRASH => 'Kosz', + MailboxesParser::TEMPLATES => 'Szablony', + MailboxesParser::ARCHIVES => 'Archiwum', +]; diff --git a/tests/AbstractTest.php b/tests/AbstractTest.php index 4825b141..b8a39a5e 100644 --- a/tests/AbstractTest.php +++ b/tests/AbstractTest.php @@ -37,7 +37,7 @@ final protected function createConnection(): Connection final protected function createMailbox(Connection $connection = null): Mailbox { $connection = $connection ?? $this->getConnection(); - $this->mailboxName = \uniqid('mailbox_' . self::SPECIAL_CHARS); + $this->mailboxName = \uniqid('INBOX.mailbox_' . self::SPECIAL_CHARS); return $connection->createMailbox($this->mailboxName); } diff --git a/tests/MailboxesParser/MailboxesParserTest.php b/tests/MailboxesParser/MailboxesParserTest.php new file mode 100644 index 00000000..25a5c0d5 --- /dev/null +++ b/tests/MailboxesParser/MailboxesParserTest.php @@ -0,0 +1,90 @@ +mailboxes = [ + $this->createMailboxMock('INBOX.Drafts'), + $this->createMailboxMock('INBOX.Sent'), + $this->createMailboxMock('INBOX.Sent.sub'), + $this->createMailboxMock('INBOX.normal'), + $this->createMailboxMock('INBOX'), + $this->createMailboxMock('INBOX.normal.sub'), + $this->createMailboxMock('INBOX.trash'), + $this->createMailboxMock('Outside'), + ]; + } + + public function testParser() + { + $parser = new MailboxesParser($this->mailboxes); + $folders = $parser->getFolders(); + $this->assertCount(8, $folders); + $this->assertSame('INBOX', $folders[0]->getMailboxName()); + $this->assertSame('Inbox', $folders[0]->getName()); + $this->assertInstanceOf(MailboxInterface::class, $folders[0]->getMailbox()); + $order = []; + /** @var ParsedMailbox $parsedMailbox */ + foreach ($folders as $parsedMailbox) { + $order[] = ['order' => $parsedMailbox->getOrder(), 'name' => $parsedMailbox->getMailboxName()]; + } + $expected = [ + ['order' => 1, 'name' => 'INBOX'], + ['order' => 2, 'name' => 'INBOX.Sent'], + ['order' => 2.0001, 'name' => 'INBOX.Sent.sub'], + ['order' => 3, 'name' => 'INBOX.Drafts'], + ['order' => 10, 'name' => 'Outside'], + ['order' => 101, 'name' => 'INBOX.normal'], + ['order' => 101.0001, 'name' => 'INBOX.normal.sub'], + ['order' => 30000, 'name' => 'INBOX.trash'], + ]; + $this->assertSame($expected, $order); + } + + public function testSetLanguage() + { + $mailboxes = [ + $this->createMailboxMock('INBOX'), + $this->createMailboxMock('INBOX.Wysłane'), + ]; + $parser = new MailboxesParser($mailboxes); + $parser->setLanguage('pl'); + $folders = $parser->getFolders(); + $this->assertSame('INBOX', $folders[0]->getMailboxName()); + $this->assertSame('Odebrane', $folders[0]->getName()); + $this->assertSame('INBOX.Wysłane', $folders[1]->getMailboxName()); + $this->assertSame('Wysłane', $folders[1]->getName()); + } + + private function createMailboxMock($mailboxName) + { + $mailbox = $this->createMock(MailboxInterface::class); + $mailbox->method('getName') + ->willReturn($mailboxName); + + $mailbox->method('getDelimiter') + ->willReturn('.'); + + return $mailbox; + } +} diff --git a/tests/MailboxesParser/MailboxesTreeParserTest.php b/tests/MailboxesParser/MailboxesTreeParserTest.php new file mode 100644 index 00000000..e635441c --- /dev/null +++ b/tests/MailboxesParser/MailboxesTreeParserTest.php @@ -0,0 +1,111 @@ +createMailboxMock('inbox'), + $this->createMailboxMock('inbox.first'), + $this->createMailboxMock('inbox.second'), + $this->createMailboxMock('inbox.second.other'), + $this->createMailboxMock('inbox.third.another'), + ]; + $parser = new MailboxesParser($mailboxes); + $folders = $parser->getFolders(); + $parser = new MailboxesTreeParser(); + $zwroc = $parser->parse($folders); + $spodziewane = [ + 'inbox' => [ + 'subfolders' => [ + 'first' => [ + 'subfolders' => [], + 'mailboxName' => 'inbox.first', + 'name' => 'First', + ], + 'second' => [ + 'subfolders' => [ + 'other' => [ + 'subfolders' => [], + 'mailboxName' => 'inbox.second.other', + 'name' => 'Other', + ], + ], + 'mailboxName' => 'inbox.second', + 'name' => 'Second', + ], + 'third' => [ + 'subfolders' => [ + 'another' => [ + 'subfolders' => [], + 'mailboxName' => 'inbox.third.another', + 'name' => 'Another', + ], + ], + ], + ], + 'mailboxName' => 'inbox', + 'name' => 'Inbox', + ], + ]; + $this->assertSame($spodziewane, $zwroc); + } + +// public function testTreeParserWithDashDelimiter() +// { +// $dane = [ +// 'inbox' => ['name' => 'Inbox'], +// 'inbox|first' => ['name' => 'First'], +// 'inbox|second' => ['name' => 'Second'], +// ]; +// $parser = new MailboxesTreeParser(); +// $zwroc = $parser->parse($dane, '|'); +// $spodziewane = [ +// 'inbox' => [ +// 'name' => 'Inbox', +// 'mailboxName' => 'inbox', +// 'subfolders' => [ +// 'first' => [ +// 'name' => 'First', +// 'mailboxName' => 'inbox|first', +// 'subfolders' => [], +// ], +// 'second' => [ +// 'name' => 'Second', +// 'mailboxName' => 'inbox|second', +// 'subfolders' => [], +// ], +// ], +// ], +// ]; +// $this->assertEquals($spodziewane, $zwroc); +// } + + private function createMailboxMock($mailboxName) + { + $mailbox = $this->createMock(MailboxInterface::class); + $mailbox->method('getName') + ->willReturn($mailboxName); + + $mailbox->method('getDelimiter') + ->willReturn('.'); + + return $mailbox; + } +}