sfaut/zimbra

此包最新版本(v1.2.0)没有可用的许可证信息。

使用PHP简单地搜索、读取和发送Zimbra消息。附件得到管理。

v1.2.0 2024-07-02 22:37 UTC

This package is auto-updated.

Last update: 2024-10-02 23:10:55 UTC


README

简单读取和发送Zimbra消息。附件得到管理。

基于Zimbra 8 SOAP API

简介

  • 电子邮件消息是多才多艺的。《sfaut\Zimbra》是一个相对较低级别的类,其目的是提供一个简单的匿名对象,代表消息及其主要组件。《sfaut\Zimbra》还提供了一些辅助方法来发送和获取消息、上传和下载附件、探索目录结构以及进行搜索。
  • 简单至上 - 烧毁并遗忘

Composer安装

您可以使用Composer将《sfaut\Zimbra》包添加到您的项目中

> composer require sfaut/zimbra

并像通常一样使用自动加载器包含它

<?php

require_once '/path/to/vendor/autoload.php';

$zimbra = sfaut\Zimbra::authenticate(...);

// ...

原始安装

《sfaut\Zimbra》是一个独特的类,没有依赖项,因此您可以下载/src/Zimbra.php并将其包含在您的脚本中,就像其他脚本一样。

<?php

require_once '/path/to/sfaut/src/Zimbra.php';

$zimbra = sfaut\Zimbra::authenticate(...);

// ...

对象结构

以下是在消息数组中的搜索响应结构。消息是一个匿名对象。

[
    {
        id: <Message ID, useful for internal usage like attachment download>
        mid: <Another message ID, useful for querying a specific message>
        folder: <Folder ID>
        conversation: <Conversation ID>
        timestamp: <Message creation, format "Y-m-d H:i:s">
        subject: <Message subject>
        addresses: {
            to: [...]
            from: [...]
            cc: [...]
        }
        fragment: <Fragment of the message>
        flags: <Message flags>
        size: <Message size, in bytes>
        body: <Message body>
        attachments: [
            {
                part: <Attachment's part message>
                disposition: <MIME disposition, "inline" or "attachment">
                type: <MIME type, eg. "text/csv">
                size: <Attachment size, in bytes>
                basename: <Attachment basename (with extension), eg. "Report.csv">
                filename: <Attachment filename (without extension), eg. "Report">
                extension: <Attachment extension without dot, eg. "csv">
            }
            ...
        ]
    }
    ...
]

连接

静态方法Zimbra::authenticate()创建一个新的sfaut\Zimbra实例并立即连接到Zimbra服务器。如果失败,将引发异常。

<?php

use sfaut\Zimbra;

require_once '/path/to/sfaut/src/Zimbra.php';

$host = 'https://zimbra.example.net';
$user = 'root@example.net';
$password = 'M;P455w0r|)';

$zimbra = Zimbra::authenticate($host, $user, $password);

为了缩短以下示例,将剪掉不在作用域内的部分,如userequire等。假设sfaut\Zimbra$zimbra中实例化。

错误管理

sfaut\Zimbra是以异常为导向的。因此,您应该在try / catch / finally块中封装语句。

try {
    $zimbra = Zimbra::authenticate($host, $user, $password);
    // ...
} catch (Exception $e) {
    echo $e->getMessage();
    exit(1);
}

为了缩短以下示例,将剪掉异常管理部分。

获取邮箱消息

实际上,所有消息列表都是搜索结果。搜索是通过Zimbra::search()方法和Zimbra客户端搜索能力执行的。Zimbra搜索技巧已存档在此gist中

$folder = '/Inbox';
$messages = $zimbra->search(['in' => $folder]);
foreach ($messages as $i => $message) {
    printf(
        "%6d %s %40s %s\r\n",
        $message->id, $message->timestamp,
        $message->from[0], $message->subject,
    );
}

发送消息

$addresses = ['to' => 'user@example.net'];

$subject = 'Hello!';

$body = <<<BUFFER
    Dear Sir,\n
    URGENT BUSINESS PROPOSAL\n
    ...
    BUFFER;

$zimbra->send($addresses, $subject, $body);

向多个收件人发送消息

您可以使用数组指定多个电子邮件地址

// 1 mail to 3 direct recipients, 1 carbon copy, 1 blind carbon copy
// Response to trash
$addresses = [
    'to' => ['user1@example.net', 'user2@example.net', 'user3@example.net'],
    'cc' => 'ml@example.net',
    'bcc' => 'archive@example.net',
    'r' => 'trash@example.net',
];

$zimbra->send($addresses, $subject, $body);

带有附件发送消息

第4个Zimbra::send()参数是一个包含attachments的数组。

基本上,一个attachments是一个包含2个属性的匿名对象(或数组)

  • basename:附加文件的名称和扩展名
  • type:包含附件的自然属性

可能的type

  • file:该值表示要附加的文件的完整路径
  • buffer:该值表示要附加的原始数据
  • stream:该值是要附加的流资源
  • 附件ID:在先前上传文件时给出的字符串

将本地文件附加到消息上

$attachments = [
    ['basename' => 'data.csv', 'file' => '/path/to/data.csv'],
];

$zimbra->send($addresses, $subject, $body, $attachments);

您也可以连续附加多个文件

$attachments = [
    ['basename' => 'data-1.csv', 'file' => '/path/to/data-1.csv'],
    ['basename' => 'data-2.csv', 'file' => '/path/to/data-2.csv'],
    ['basename' => 'data-3.csv', 'file' => '/path/to/data-3.csv'],
];

$zimbra->send($addresses, $subject, $body, $attachments);

并且您可以混合不同类型的数据源

$buffer = 'Contents that will be attached to a file';

$stream = fopen('/path/to/file.csv', 'r');

$attachments = [
    ['basename' => 'data-1.csv', 'file' => '/path/to/data.csv'],
    ['basename' => 'data-2.txt', 'buffer' => $buffer],
    ['basename' => 'data-3.csv', 'stream' => $stream],
];

$zimbra->send($addresses, $subject, $body, $attachments);

在发送消息时上传每个附件。如果您发送具有相同附件的多个消息,这可能会浪费资源。为了节省资源,您可以使用Zimbra::upload()首先上传文件,然后将它们附加到消息中。

// ⚠️ YOU SHOULD NOT DO THIS
// The same file uploaded 3 times for 3 messages
$attachments = [
    ['basename' => 'decennial-data.csv', 'file' => '/path/to/decennial-data.csv'],
    ['basename' => 'another-data.csv', 'file' => '/path/to/another-data.csv'],
];
$zimbra->send($addresses_1, $subject, $body, $attachments); // 🙅🏻‍♂️
$zimbra->send($addresses_2, $subject, $body, $attachments); // 🙅🏻‍♂️
$zimbra->send($addresses_3, $subject, $body, $attachments); // 🙅🏻‍♂️

// 💡 YOU SHOULD DO THAT
// 1 upload for 3 messages
$attachments = [
    ['basename' => 'decennial-data.csv', 'file' => '/path/to/decennial-data.csv'],
    ['basename' => 'another-data.csv', 'file' => '/path/to/another-data.csv'],
];

// 🥂 That's the trick
// An attachment ID is retrieved and reused as necessary
$attachments = $zimbra->upload($attachments);

$zimbra->send($addresses_1, $subject, $body, $attachments);
$zimbra->send($addresses_2, $subject, $body, $attachments);
$zimbra->send($addresses_3, $subject, $body, $attachments);

附件

消息附件在数组$message->attachments中指定。

附件可以通过 Zimbra::upload() 上传,并通过 Zimbra::download() 下载。

每个附件都是一个匿名对象,具有以下结构

{
    part: <MIME part of the attachment in the message, eg. "2" or "2.1.1">
    disposition: <Attachment method to the message : "attachment" or "inline">
    type: <MIME type of the attachment file, eg. "text/plain", "text/csv">
    size: <Attachement file size in bytes>
    basename: <Attachement file name with extension, eg. "my-data.csv">
    filename: <Attachment file name without extension, eg. "my-data">
    extension: <Attachment extension without dot, eg. "csv">
    stream: <Stream from temporary, only after Zimbra::download() call>
}

附件检索

消息的所有附件都通过 Zimbra::download() 返回的数组检索。附件以临时文件的形式下载。这些临时文件在脚本结束时自动删除。每个附件都是一个匿名对象,其中包含指向临时文件的 stream 属性,并且可以读写。

$message = $zimbra->search(['subject' => 'Annual result 2022'])[0]; // Get 1 message
$attachment = $zimbra->download($message)[0]; // Get 1 attachment

// Where you want to save your file
$destination_file = '/path/to/' . $attachment->basename;

// 1st method, with stream
// Memory efficient
$destination_stream = fopen($destination_file, 'w');
stream_copy_to_stream($attachment->stream, $destination_stream);

// 2nd method, with buffer
// Memory inefficient on huge files, but you can process $buffer
$buffer = stream_get_contents($attachment->stream);
file_put_contents($destination_file, $buffer);

您可以使用接受附件对象作为参数的闭包来过滤要检索的附件(默认返回 true)。

$message = $zimbra->search(['subject' => 'Summer 2022 holidays photos'])[0];

// Your filter closure
// You need to keep only images attachments
$filter = fn ($attachment) => strpos($attachment->type, 'image/') === 0;

$attachments = $zimbra->download($message, $filter);

foreach ($attachments as $attachment) {
    // Save the downloaded attachments / photos to a safe place
    $stream_destination = fopen('/home/me/holidays/' . $attachment->basename, 'w');
    stream_copy_to_stream($attachment->stream, $stream_destination);
}

实际应用案例

批量下载附件

  • 您需要下载大量消息的 CSV 附件,截止日期:昨天
  • 消息存储在 /Inbox/Reports 文件夹中的邮箱中
  • 每条消息可以有 0 到 n 个附件
  • 附件可以是任何类型,如 .csv.xlsx.pdf 等,您只需要检索 .csv
  • CSV 文件的命名格式如下: Report Y-m-d.csv,例如:Report 2022-03-06.csv
  • 文件名及其扩展名可以是小写、大写或混合,您需要管理这一点
  • 您必须下载从 2020-01-01 开始的所有 CSV 附件,例如,不下载 Report 2019-12-31.csv,而下载 Report 2020-01-01.csv
  • 文件很多,所以您必须将它们保存为 Gzip 文件
  • 每条消息的主题是唯一的,但每个附件名称不是,因此下载的附件必须以 Message subject -- Attachment basename.gz 格式命名,例如:Report 2020-01-01.csv.gz
  • 目标目录是本地子目录 /mailbox/reports

PHP 和 sfaut\Zimbra 可以让您轻松地做到这一点 :)

<?php

use sfaut\Zimbra;

require_once '/path/to/vendor/autoload.php';
require_once '/path/to/settings.php';

// Starting attachment, you choose an all upper case reference
$starting_file = 'REPORT 2020-01-01.CSV';

// Mailbox source folder
$source_folder = '/Inbox/Reports';

// Locale target subdirectory, where all CSV attachments will be downloaded
$target_directory = '/path/to/mailbox/reports';

$zimbra = Zimbra::authenticate($host, $user, $password);

// Search messages in source folder that have at least one CSV attachment begining with "Report"
// You reduce unuseful messages retrieving
$messages = $zimbra->search(['in' => $source_folder, 'filename' => 'Report*', 'type' => 'text/csv']);

foreach ($messages as $message) {
    // Download attachments, only what you need
    $attachments = $zimbra->download($message, function ($attachment) {
        if (strtoupper($attachment->extension) !== 'CSV') {
            return false;
        }
        if (strtoupper($attachment->basename) < $starting_file) {
            return false;
        }
        return true;
    });
    // Save CSV attachments in compressed files in safe place
    foreach ($attachments as $attachment) {
        $target_file = "{$target_directory}/{$message->subject} -- {$attachment->basename}.gz";
        $target_stream = gzopen($target_file, 'w');
        stream_copy_to_stream($attachment->stream, $target_stream);
    }
}

exit(0);

这就是全部内容!🐰