1#!/usr/bin/env php
2<?php
3/***********************************************
4* File      :   z-push-admin.php
5* Project   :   Z-Push
6* Descr     :   This is a small command line
7*               client to see and modify the
8*               wipe status of Kopano users.
9*
10* Created   :   14.05.2010
11*
12* Copyright 2007 - 2016 Zarafa Deutschland GmbH
13*
14* This program is free software: you can redistribute it and/or modify
15* it under the terms of the GNU Affero General Public License, version 3,
16* as published by the Free Software Foundation.
17*
18* This program is distributed in the hope that it will be useful,
19* but WITHOUT ANY WARRANTY; without even the implied warranty of
20* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
21* GNU Affero General Public License for more details.
22*
23* You should have received a copy of the GNU Affero General Public License
24* along with this program.  If not, see <http://www.gnu.org/licenses/>.
25*
26* Consult LICENSE file for details
27************************************************/
28
29require_once 'vendor/autoload.php';
30
31/**
32 * //TODO resync of single folders of a users device
33 */
34
35/************************************************
36 * MAIN
37 */
38    define('BASE_PATH_CLI',  dirname(__FILE__) ."/");
39    set_include_path(get_include_path() . PATH_SEPARATOR . BASE_PATH_CLI);
40
41    if (!defined('ZPUSH_CONFIG')) define('ZPUSH_CONFIG', BASE_PATH_CLI . 'config.php');
42    include_once(ZPUSH_CONFIG);
43
44    try {
45        ZPush::CheckConfig();
46        ZPushAdminCLI::CheckEnv();
47        ZPushAdminCLI::CheckOptions();
48
49        if (! ZPushAdminCLI::SureWhatToDo()) {
50            // show error message if available
51            if (ZPushAdminCLI::GetErrorMessage())
52                fwrite(STDERR, ZPushAdminCLI::GetErrorMessage() . "\n");
53
54            echo ZPushAdminCLI::UsageInstructions();
55            if (ZPushAdminCLI::$help) {
56                exit(0);
57            }
58            exit(1);
59        }
60
61        ZPushAdminCLI::RunCommand();
62    }
63    catch (ZPushException $zpe) {
64        fwrite(STDERR, get_class($zpe) . ": ". $zpe->getMessage() . "\n");
65        exit(1);
66    }
67
68
69/************************************************
70 * Z-Push-Admin CLI
71 */
72class ZPushAdminCLI {
73    const COMMAND_SHOWALLDEVICES = 1;
74    const COMMAND_SHOWDEVICESOFUSER = 2;
75    const COMMAND_SHOWUSERSOFDEVICE = 3;
76    const COMMAND_WIPEDEVICE = 4;
77    const COMMAND_REMOVEDEVICE = 5;
78    const COMMAND_RESYNCDEVICE = 6;
79    const COMMAND_CLEARLOOP = 7;
80    const COMMAND_SHOWLASTSYNC = 8;
81    const COMMAND_RESYNCFOLDER = 9;
82    const COMMAND_FIXSTATES = 10;
83    const COMMAND_RESYNCHIERARCHY = 11;
84    const COMMAND_ADDSHARED = 12;
85    const COMMAND_REMOVESHARED = 13;
86    const COMMAND_LISTALLSHARES = 14;
87    const COMMAND_LISTSTORESHARES = 15;
88    const COMMAND_LISTFOLDERSHARES = 16;
89    const COMMAND_LISTFOLDERS = 17;
90    const COMMAND_LISTDETAILS = 18;
91
92    const TYPE_OPTION_EMAIL = "email";
93    const TYPE_OPTION_CALENDAR = "calendar";
94    const TYPE_OPTION_CONTACT = "contact";
95    const TYPE_OPTION_TASK = "task";
96    const TYPE_OPTION_NOTE = "note";
97    const TYPE_OPTION_HIERARCHY = "hierarchy";
98    const TYPE_OPTION_GAB = "gab";
99
100    static private $command;
101    static private $user = false;
102    static private $device = false;
103    static private $type = false;
104    static private $errormessage;
105    static private $daysold = false;
106    static private $shared = false;
107    static private $devicedriven = false;
108    static private $foldername = false;
109    static private $store = false;
110    static private $folderid = false;
111    static private $flags = 0;
112
113    static public $help = false;
114
115    /**
116     * Returns usage instructions
117     *
118     * @return string
119     * @access public
120     */
121    static public function UsageInstructions() {
122        return  "Usage:\n\tz-push-admin.php -a ACTION [options]\n\n" .
123                "Parameters:\n\t-a list/lastsync/listdetails/wipe/remove/resync/clearloop/fixstates/addshared/removeshared/listshares\n\t[-u] username\n\t[-d] deviceid\n" .
124                "\t[-t] type\tthe following types are available: '".self::TYPE_OPTION_EMAIL."', '".self::TYPE_OPTION_CALENDAR."', '".self::TYPE_OPTION_CONTACT."', '".self::TYPE_OPTION_TASK."', '".self::TYPE_OPTION_NOTE."', '".self::TYPE_OPTION_HIERARCHY."' of '".self::TYPE_OPTION_GAB."' (for KOE) or a folder id.\n" .
125                "\t[--shared|-s]\tshow detailed information about shared folders of a user in list.\n".
126                "\t[--days-old] n\tshow or remove profiles older than n days with lastsync or remove. n must be a positive integer.\n".
127                "\t[--devicedriven]\texecute the fixstates device driven. Recommended for specific enviroments with high traffic.\n\n".
128                "Actions:\n" .
129                "\tlist\t\t\t\t\t Lists all devices and synchronized users.\n" .
130                "\tlist -u USER\t\t\t\t Lists all devices of user USER.\n" .
131                "\tlist -d DEVICE\t\t\t\t Lists all users of device DEVICE.\n" .
132                "\tlastsync\t\t\t\t Lists all devices and synchronized users and the last synchronization time.\n" .
133                "\tlistdetails\t\t\t\t Lists all synchronized devices-users and their details in a tab separated list.\n" .
134                "\twipe -u USER\t\t\t\t Remote wipes all devices of user USER.\n" .
135                "\twipe -d DEVICE\t\t\t\t Remote wipes device DEVICE.\n" .
136                "\twipe -u USER -d DEVICE\t\t\t Remote wipes device DEVICE of user USER.\n" .
137                "\tremove -u USER\t\t\t\t Removes all state data of all devices of user USER.\n" .
138                "\tremove -d DEVICE\t\t\t Removes all state data of all users synchronized on device DEVICE.\n" .
139                "\tremove -u USER -d DEVICE\t\t Removes all related state data of device DEVICE of user USER.\n" .
140                "\tresync -u USER -d DEVICE\t\t Resynchronizes all data of device DEVICE of user USER.\n" .
141                "\tresync -t TYPE \t\t\t\t Resynchronizes all folders of type (possible values above) for all devices and users.\n" .
142                "\tresync -t TYPE -u USER \t\t\t Resynchronizes all folders of type (possible values above) for the user USER.\n" .
143                "\tresync -t TYPE -u USER -d DEVICE\t Resynchronizes all folders of type (possible values above) for a specified device and user.\n" .
144                "\tresync -t FOLDERID -u USER\t\t Resynchronize the specified folder id only. The USER should be specified for better performance.\n" .
145                "\tresync -t hierarchy -u USER -d DEVICE\t Resynchronize the folder hierarchy data for an optional USER and optional DEVICE.\n" .
146                "\tclearloop\t\t\t\t Clears system wide loop detection data.\n" .
147                "\tclearloop -d DEVICE -u USER\t\t Clears all loop detection data of a device DEVICE and an optional user USER.\n" .
148                "\tfixstates\t\t\t\t Checks the states for integrity and fixes potential issues.\n" .
149                "\tfixstates -u USER\t\t\t Checks the states for integrity and fixes potential issues of user USER.\n\n" .
150                "\taddshared -u USER -d DEVICE -n FOLDERNAME -o STORE -t TYPE -f FOLDERID -g FLAGS\n" .
151                        "\t\t\t\t\t\t Adds a shared folder for a user.\n" .
152                        "\t\t\t\t\t\t USER is required. If no DEVICE is given, the shared folder will be added to all of the devices of the user.\n" .
153                        "\t\t\t\t\t\t FOLDERNAME the name of the shared folder. STORE - where this folder is located, e.g. \"SYSTEM\" (for public folder) or a username.\n" .
154                        "\t\t\t\t\t\t TYPE is the folder type of the shared folder (possible values above, except 'hierarchy' and 'gab').\n" .
155                        "\t\t\t\t\t\t FOLDERID is the id of shared folder.\n" .
156                        "\t\t\t\t\t\t FLAGS is optional (default: '0'). Make sure you separate -g and value with \"=\", e.g. -g=4.\n" .
157                        "\t\t\t\t\t\t Possible values for FLAGS: 0(none), 1 (Send-As from this folder), 4 (show calendar reminders for this folder), 8 (don't send notification emails for changes\n" .
158                        "\t\t\t\t\t\t if the folder is read-only) and all bitwise or combinations of these flags.\n" .
159                "\tremoveshared -u USER -d DEVICE -f FOLDERID\n" .
160                        "\t\t\t\t\t\t Removes a shared folder for a user.\n" .
161                        "\t\t\t\t\t\t USER is required. If no DEVICE is given, the shared folder will be removed from all of the devices of the user.\n" .
162                        "\t\t\t\t\t\t FOLDERID is the id of shared folder.\n" .
163                "\tlistshares -o STORE -f FOLDERID\n".
164                        "\t\t\t\t\t\t Lists opened shared folders and who opened them on which device.\n" .
165                        "\t\t\t\t\t\t STORE and FOLDERID are optional. If they're not provided then the script will display all open shares.\n" .
166                        "\t\t\t\t\t\t STORE - whose shared folders to list, e.g. \"SYSTEM\" (for public folders) or a username.\n" .
167                        "\t\t\t\t\t\t FOLDERID - list who opened the shared folder.\n" .
168                        "\t\t\t\t\t\t If both STORE and FOLDERID are provided the script will only list who opened the folder ignoring the STORE parameter.\n" .
169                "\tlistfolders -u USER -d DEVICE\n".
170                        "\t\t\t\t\t\t Returns each folder and FOLDERID of user USER and device DEVICE. Useful for getting FOLDERID to be used with the command: resync -t FOLDERID -u USER.\n".
171                        "\t\t\t\t\t\t Note that if a device is offline, broken or not being synched for some time, this list will not be updated. If folders were created/renamed/removed\n".
172                        "\t\t\t\t\t\t after the last synchronization, this will not be reflected in this list.\n".
173                "\n";
174    }
175
176    /**
177     * Checks the environment
178     *
179     * @return
180     * @access public
181     */
182    static public function CheckEnv() {
183        if (php_sapi_name() != "cli")
184            self::$errormessage = "This script can only be called from the CLI.";
185
186        if (!function_exists("getopt"))
187            self::$errormessage = "PHP Function getopt not found. Please check your PHP version and settings.";
188    }
189
190    /**
191     * Checks the options from the command line
192     *
193     * @return
194     * @access public
195     */
196    static public function CheckOptions() {
197        if (self::$errormessage)
198            return;
199
200        $options = getopt("u:d:a:t:sn:o:f:g::h", array('user:', 'device:', 'action:', 'type:', 'days-old:', 'days-ago:', 'shared', 'foldername:', 'store', 'folderid:', 'flags::', 'devicedriven', 'help'));
201
202        // get 'user'
203        if (isset($options['u']) && !empty($options['u']))
204            self::$user = strtolower(trim($options['u']));
205        else if (isset($options['user']) && !empty($options['user']))
206            self::$user = strtolower(trim($options['user']));
207
208        // get 'device'
209        if (isset($options['d']) && !empty($options['d']))
210            self::$device = strtolower(trim($options['d']));
211        else if (isset($options['device']) && !empty($options['device']))
212            self::$device = strtolower(trim($options['device']));
213
214        // get 'action'
215        $action = false;
216        if (isset($options['a']) && !empty($options['a']))
217            $action = strtolower(trim($options['a']));
218        elseif (isset($options['action']) && !empty($options['action']))
219            $action = strtolower(trim($options['action']));
220
221        // get 'type'
222        if (isset($options['t']) && !empty($options['t']))
223            self::$type = strtolower(trim($options['t']));
224        elseif (isset($options['type']) && !empty($options['type']))
225            self::$type = strtolower(trim($options['type']));
226
227        if (isset($options['days-ago']) && !empty($options['days-ago'])) {
228            $options['days-old'] = $options['days-ago'];
229        }
230
231        if (isset($options['days-old']) && !empty($options['days-old'])) {
232            if (!is_numeric($options['days-old']) || $options['days-old'] < 0) {
233                self::$errormessage = "--days-old parameter must be a positive integer\n";
234                self::$command = null;
235                return;
236            }
237            self::$daysold = trim($options['days-old']);
238        }
239
240        if (isset($options['s']) || isset($options['shared'])) {
241            self::$shared = true;
242        }
243
244        if (isset($options['devicedriven'])) {
245            if (self::$user){
246                self::$errormessage = "--devicedriven doesn't accept the user -u parameter.\n";
247                return;
248            }
249            self::$devicedriven = true;
250        }
251
252        // get 'foldername'
253        if (isset($options['n']) && !empty($options['n']))
254            self::$foldername = trim($options['n']);
255        elseif (isset($options['foldername']) && !empty($options['foldername']))
256            self::$foldername = trim($options['foldername']);
257
258        // get 'store'
259        if (isset($options['o']) && !empty($options['o']))
260            self::$store = trim($options['o']);
261        elseif (isset($options['store']) && !empty($options['store']))
262            self::$store = trim($options['store']);
263
264        // get 'folderid'
265        if (isset($options['f']) && !empty($options['f']))
266            self::$folderid = trim($options['f']);
267        elseif (isset($options['folderid']) && !empty($options['folderid']))
268            self::$folderid = trim($options['folderid']);
269
270        // get 'flags'
271        if (isset($options['flags'])) {
272            $options['g'] = $options['flags'];
273        }
274
275        if (isset($options['g'])) {
276            $flags = intval($options['g']);
277            if ($flags == DeviceManager::FLD_FLAGS_NONE || ($flags & (DeviceManager::FLD_FLAGS_SENDASOWNER | DeviceManager::FLD_FLAGS_CALENDARREMINDERS | DeviceManager::FLD_FLAGS_NOREADONLYNOTIFY))) {
278                self::$flags = $flags;
279            }
280            else {
281                self::$flags = false;
282                self::$errormessage = "Possible values for FLAGS: 0(none), 1 (Send-As from this folder), 4 (show calendar reminders for this folder), 5 (combination of Send-as and calendar reminders).\n";
283            }
284        }
285
286        // if type is set, it must be one of known types or a 44 or 48 byte long folder id
287        if (self::$type !== false) {
288            if (self::$type !== self::TYPE_OPTION_EMAIL &&
289                self::$type !== self::TYPE_OPTION_CALENDAR &&
290                self::$type !== self::TYPE_OPTION_CONTACT &&
291                self::$type !== self::TYPE_OPTION_TASK &&
292                self::$type !== self::TYPE_OPTION_NOTE &&
293                self::$type !== self::TYPE_OPTION_HIERARCHY &&
294                self::$type !== self::TYPE_OPTION_GAB &&
295                strlen(self::$type) !== 6 &&       // like U1f38d
296                strlen(self::$type) !== 44 &&
297                strlen(self::$type) !== 48) {
298                    self::$errormessage = "Wrong 'type'. Possible values are: ".
299                        "'".self::TYPE_OPTION_EMAIL."', '".self::TYPE_OPTION_CALENDAR."', '".self::TYPE_OPTION_CONTACT."', '".self::TYPE_OPTION_TASK."', '".self::TYPE_OPTION_NOTE."', '".self::TYPE_OPTION_HIERARCHY."', '".self::TYPE_OPTION_GAB."' ".
300                        "or a 6, 44 or 48 byte long folder id (as hex).";
301                    return;
302                }
303        }
304
305        if ((isset($options['h']) || isset($options['help'])) && $action === false) {
306            self::$help = true;
307            $action = 'help';
308        }
309
310        // get a command for the requested action
311        switch ($action) {
312            // list data
313            case "list":
314                if (self::$user === false && self::$device === false)
315                    self::$command = self::COMMAND_SHOWALLDEVICES;
316
317                if (self::$user !== false)
318                    self::$command = self::COMMAND_SHOWDEVICESOFUSER;
319
320                if (self::$device !== false)
321                    self::$command = self::COMMAND_SHOWUSERSOFDEVICE;
322                break;
323
324            // list data
325            case "lastsync":
326                self::$command = self::COMMAND_SHOWLASTSYNC;
327                break;
328
329            // list details
330            case "listdetails":
331                self::$command = self::COMMAND_LISTDETAILS;
332                break;
333
334            // remove wipe device
335            case "wipe":
336                if (self::$user === false && self::$device === false)
337                    self::$errormessage = "Not possible to execute remote wipe. Device, user or both must be specified.";
338                else
339                    self::$command = self::COMMAND_WIPEDEVICE;
340                break;
341
342            // remove device data of user
343            case "remove":
344                if (self::$user === false && self::$device === false)
345                    self::$errormessage = "Not possible to remove data. Device, user or both must be specified.";
346                else
347                    self::$command = self::COMMAND_REMOVEDEVICE;
348                break;
349
350            // resync a device
351            case "resync":
352            case "re-sync":
353            case "sync":
354            case "resynchronize":
355            case "re-synchronize":
356            case "synchronize":
357                // full resync
358                if (self::$type === false) {
359                    if (self::$user === false || self::$device === false)
360                        self::$errormessage = "Not possible to resynchronize device. Device and user must be specified.";
361                    else
362                        self::$command = self::COMMAND_RESYNCDEVICE;
363                }
364                else if (self::$type === self::TYPE_OPTION_HIERARCHY) {
365                    self::$command = self::COMMAND_RESYNCHIERARCHY;
366                }
367                else {
368                    self::$command = self::COMMAND_RESYNCFOLDER;
369                }
370                break;
371
372            // clear loop detection data
373            case "clearloop":
374            case "clearloopdetection":
375                self::$command = self::COMMAND_CLEARLOOP;
376                break;
377
378            // fix states
379            case "fixstates":
380            case "fix":
381                self::$command = self::COMMAND_FIXSTATES;
382                break;
383
384            case "addshared":
385                if (self::$user === false || self::$foldername === false || self::$store === false || self::$type === false || self::$folderid === false || self::$flags === false) {
386                    if (!self::$errormessage) {
387                        self::$errormessage = 'USER, FOLDERNAME, STORE, TYPE and FOLDERID are required for addshared command.';
388                    }
389                    return;
390                }
391                else {
392                    if (in_array(self::$type, array(self::TYPE_OPTION_CALENDAR, self::TYPE_OPTION_CONTACT, self::TYPE_OPTION_EMAIL, self::TYPE_OPTION_NOTE, self::TYPE_OPTION_TASK))) {
393                        self::$command = self::COMMAND_ADDSHARED;
394                    }
395                    elseif (self::$type == self::TYPE_OPTION_HIERARCHY || self::$type == self::TYPE_OPTION_GAB) {
396                        self::$errormessage = "'hierarchy' and 'gab' are not valid types for addshared action.";
397                        return;
398                    }
399                    else {
400                        self::$errormessage = sprintf("Adding folder of type '%s' is not supported.", self::$type);
401                        return;
402                    }
403                }
404                break;
405
406            case "removeshared":
407                if (self::$user === false || self::$folderid === false) {
408                    self::$errormessage = 'USER and FOLDERID are required for removeshared command.';
409                    return;
410                }
411                self::$command = self::COMMAND_REMOVESHARED;
412                break;
413
414            case "listshares":
415                if (self::$store === false && self::$device === false)
416                    self::$command = self::COMMAND_LISTALLSHARES;
417
418                if (self::$store !== false)
419                    self::$command = self::COMMAND_LISTSTORESHARES;
420
421                if (self::$folderid !== false)
422                    self::$command = self::COMMAND_LISTFOLDERSHARES;
423                break;
424
425            case "listfolders":
426                self::$command = self::COMMAND_LISTFOLDERS;
427                break;
428
429            case "help":
430                break;
431
432            default:
433                self::UsageInstructions();
434                self::$help = false;
435        }
436    }
437
438    /**
439     * Indicates if the options from the command line
440     * could be processed correctly
441     *
442     * @return boolean
443     * @access public
444     */
445    static public function SureWhatToDo() {
446        return isset(self::$command);
447    }
448
449    /**
450     * Returns a errormessage of things which could have gone wrong
451     *
452     * @return string
453     * @access public
454     */
455    static public function GetErrorMessage() {
456        return (isset(self::$errormessage))?self::$errormessage:"";
457    }
458
459    /**
460     * Runs a command requested from an action of the command line
461     *
462     * @return
463     * @access public
464     */
465    static public function RunCommand() {
466        echo "\n";
467        switch(self::$command) {
468            case self::COMMAND_SHOWALLDEVICES:
469                self::CommandShowDevices();
470                break;
471
472            case self::COMMAND_SHOWDEVICESOFUSER:
473                self::CommandShowDevices();
474                break;
475
476            case self::COMMAND_SHOWUSERSOFDEVICE:
477                self::CommandDeviceUsers();
478                break;
479
480            case self::COMMAND_SHOWLASTSYNC:
481                self::CommandShowLastSync();
482                break;
483
484            case self::COMMAND_LISTDETAILS:
485                self::CommandListDetails();
486                break;
487
488            case self::COMMAND_WIPEDEVICE:
489                if (self::$device)
490                    echo sprintf("Are you sure you want to REMOTE WIPE device '%s' [y/N]: ", self::$device);
491                else
492                    echo sprintf("Are you sure you want to REMOTE WIPE all devices of user '%s' [y/N]: ", self::$user);
493
494                $confirm  =  strtolower(trim(fgets(STDIN)));
495                if ( $confirm === 'y' || $confirm === 'yes')
496                    self::CommandWipeDevice();
497                else
498                    echo "Aborted!\n";
499                break;
500
501            case self::COMMAND_REMOVEDEVICE:
502                self::CommandRemoveDevice();
503                break;
504
505            case self::COMMAND_RESYNCDEVICE:
506                if (self::$device == false) {
507                    echo sprintf("Are you sure you want to re-synchronize all devices of user '%s' [y/N]: ", self::$user);
508                    $confirm  =  strtolower(trim(fgets(STDIN)));
509                    if ( !($confirm === 'y' || $confirm === 'yes')) {
510                        echo "Aborted!\n";
511                        exit(1);
512                    }
513                }
514                self::CommandResyncDevices();
515                break;
516
517            case self::COMMAND_RESYNCFOLDER:
518                if (self::$device == false && self::$user == false) {
519                    echo "Are you sure you want to re-synchronize this folder type of all devices and users [y/N]: ";
520                    $confirm  =  strtolower(trim(fgets(STDIN)));
521                    if ( !($confirm === 'y' || $confirm === 'yes')) {
522                        echo "Aborted!\n";
523                        exit(1);
524                    }
525                }
526                self::CommandResyncFolder();
527                break;
528
529            case self::COMMAND_RESYNCHIERARCHY:
530                if (self::$device == false && self::$user == false) {
531                    echo "Are you sure you want to re-synchronize the hierarchy of all devices and users [y/N]: ";
532                    $confirm  =  strtolower(trim(fgets(STDIN)));
533                    if ( !($confirm === 'y' || $confirm === 'yes')) {
534                        echo "Aborted!\n";
535                        exit(1);
536                    }
537                }
538                self::CommandResyncHierarchy();
539                break;
540
541            case self::COMMAND_CLEARLOOP:
542                self::CommandClearLoopDetectionData();
543                break;
544
545            case self::COMMAND_FIXSTATES:
546                if (self::$user === false) {
547                    self::CommandFixStates();
548                }
549                else {
550                    self::CommandFixStates(self::$user);
551                }
552                break;
553
554            case self::COMMAND_ADDSHARED:
555                self::CommandAddShared();
556                break;
557
558            case self::COMMAND_REMOVESHARED:
559                self::CommandRemoveShared();
560                break;
561
562            case self::COMMAND_LISTALLSHARES:
563            case self::COMMAND_LISTSTORESHARES:
564            case self::COMMAND_LISTFOLDERSHARES:
565                self::CommandListShares();
566                break;
567
568            case self::COMMAND_LISTFOLDERS:
569                self::CommandListFolders();
570                break;
571        }
572        echo "\n";
573    }
574
575    /**
576     * Command "Show all devices" and "Show devices of user"
577     * Prints the device id of/and connected users
578     *
579     * @return
580     * @access public
581     */
582    static public function CommandShowDevices() {
583        $devicelist = ZPushAdmin::ListDevices(self::$user);
584        if (empty($devicelist))
585            echo "\tno devices found\n";
586        else {
587            if (self::$user === false) {
588                echo "All synchronized devices\n\n";
589                echo str_pad("Device id", 36). "Synchronized users\n";
590                echo "-----------------------------------------------------\n";
591            }
592            else
593                echo "Synchronized devices of user: ". self::$user. "\n";
594        }
595
596        foreach ($devicelist as $deviceId) {
597            if (self::$user === false) {
598                echo str_pad($deviceId, 36) . implode (",", ZPushAdmin::ListUsers($deviceId)) ."\n";
599            }
600            else
601                self::printDeviceData($deviceId, self::$user);
602        }
603    }
604
605    /**
606     * Command "Show all devices and users with last sync time"
607     * Prints the device id of/and connected users
608     *
609     * @return
610     * @access public
611     */
612     static public function CommandShowLastSync() {
613        $devicelist = ZPushAdmin::ListDevices(false);
614        if (empty($devicelist))
615            echo "\tno devices found\n";
616        else {
617            echo "All known devices and users and their last synchronization time\n\n";
618            echo str_pad("Device id", 36) . str_pad("Synchronized user", 31) . str_pad("Last sync time", 33) . "Short Ids\n";
619            echo "------------------------------------------------------------------------------------------------------------------\n";
620        }
621
622        $now = time();
623        foreach ($devicelist as $deviceId) {
624            $users = ZPushAdmin::ListUsers($deviceId);
625            foreach ($users as $user) {
626                $device = ZPushAdmin::GetDeviceDetails($deviceId, $user);
627                $daysOld = floor(($now - $device->GetLastSyncTime()) / 86400);
628                if (self::$daysold > $daysOld) {
629                    continue;
630                }
631                $lastsync = $device->GetLastSyncTime() ? strftime("%Y-%m-%d %H:%M", $device->GetLastSyncTime()) . ' (' . str_pad($daysOld, 3, ' ', STR_PAD_LEFT) . ' days ago)' : "never";
632                $hasShortFolderIds = $device->HasFolderIdMapping() ? "Yes":"No";
633                echo str_pad($deviceId, 36) . str_pad($user, 30) . " " . str_pad($lastsync, 33) . $hasShortFolderIds . "\n";
634            }
635        }
636    }
637
638    /**
639     * Command "Lists all synchronized devices-users and their details in a tab separated list"
640     *
641     * Prints the device id of/and connected users:
642     * - Device id
643     * - Synchronized users
644     * - Last sync time
645     * - deviceType
646     * - deviceModel
647     * - deviceOS
648     * - ASVersion
649     * - KoeVersion
650     * - Total folders
651     * - Synchronized folders
652     * - Not synchronized folders
653     * - Shared/impersonated folders
654     * - Ignored messages
655     * - KOE inactive
656     *
657     * @return
658     * @access public
659     */
660    static public function CommandListDetails() {
661
662        $devicelist = ZPushAdmin::ListDevices();
663        if (empty($devicelist))
664            echo "\tno devices found\n";
665        else {
666            echo "All synchronized devices\n\n",
667            "Device id\t",
668            "Synchronized user\t",
669            "Last sync time\t",
670            "deviceType\t",
671            "UserAgent\t",
672            "deviceModel\t",
673            "deviceOS\t",
674            "ASVersion\t",
675            "KoeVersion\t",
676            "Total folders\t",
677            "Synchronized folders\t",
678            "Not synchronized folders\t",
679            "Shared/impersonated folders\t",
680            "Ignored messages\t",
681            "KOE inactive\t\n",
682            str_repeat('-', 163), "\n";
683        }
684        $now = time();
685        foreach ($devicelist as $deviceId) {
686            $users = ZPushAdmin::ListUsers($deviceId);
687            foreach ($users as $usr) {
688                $device = ZPushAdmin::GetDeviceDetails($deviceId, $usr);
689                $daysOld = floor(($now - $device->GetLastSyncTime()) / 86400);
690                if (self::$daysold > $daysOld) {
691                    continue;
692                }
693                $lastsync = $device->GetLastSyncTime() ? strftime("%Y-%m-%d %H:%M", $device->GetLastSyncTime()) . ' (' . str_pad($daysOld, 3, ' ', STR_PAD_LEFT) . ' days ago)' : "never";
694                $data = self::ListDeviceFolders($deviceId, $usr);
695                echo $deviceId, "\t",
696                $usr, "\t",
697                $lastsync, "\t",
698                ($device->GetDeviceType() !== ASDevice::UNDEFINED ? $device->GetDeviceType() : "unknown"), "\t",
699                ($device->GetDeviceUserAgent()!== ASDevice::UNDEFINED ? $device->GetDeviceUserAgent() : "unknown"), "\t",
700                $device->GetDeviceModel(), "\t",
701                $device->GetDeviceOS(), "\t",
702                ($device->GetASVersion() ? $device->GetASVersion() : "unknown"), "\t",
703                $device->GetKoeVersion(), "\t",
704                $data[0], "\t",
705                $data[1], "\t",
706                $data[2], "\t",
707                $data[3], "\t",
708				( (isset($device->ignoredmessages) && !empty($device->ignoredmessages)) ? count($device->ignoredmessages) : 0), "\t",
709                (($device->GetKoeLastAccess() && $device->GetKoeLastAccess() + 25260 < $device->GetLastSyncTime()) ? "KOE inactive":""), "\n";
710            }
711        }
712    }
713
714    /**
715     * Returns an array with the folders stats of a device id:
716     * - Total folders
717     * - Synchronized folders
718     * - Not synchronized folders
719     * - Shared/impersonated folders
720     *
721     * @return
722     * @access private
723     */
724    static private function ListDeviceFolders($deviceId, $user) {
725
726        $device = ZPushAdmin::GetDeviceDetails($deviceId, $user, true);
727        if (! $device instanceof ASDevice) {
728            printf("Folder details failed: %s\n", ZLog::GetLastMessage(LOGLEVEL_ERROR));
729            return false;
730        }
731        $folders = $device->GetAllFolderIds();
732        $synchedFolders = 0;
733        $notSynchedFolders = 0;
734        $sharedFolders = 0;
735        $hc = $device->GetHierarchyCache();
736        foreach ($folders as $folderid) {
737            if ($device->GetFolderUUID($folderid)) {
738                $synchedFolders++;
739            }
740            else {
741                $notSynchedFolders++;
742            }
743            $folder = $hc->GetFolder($folderid);
744            $name = $folder ? $folder->displayname : "unknown";
745            if (Utils::GetFolderOriginFromId($folderid) != DeviceManager::FLD_ORIGIN_USER) {
746                $sharedFolders++;
747            }
748        }
749        return array(count($folders), $synchedFolders, $notSynchedFolders, $sharedFolders);
750    }
751
752    /**
753     * Command "Show users of device"
754     * Prints informations about all users which use a device
755     *
756     * @return
757     * @access public
758     */
759    static public function CommandDeviceUsers() {
760        $users = ZPushAdmin::ListUsers(self::$device);
761
762        if (empty($users)) {
763            echo "\tno user data synchronized to device\n";
764        }
765        // if a user is specified, we only want to see the devices of this one
766        else if (self::$user !== false && !in_array(self::$user, $users)) {
767            printf("\tuser '%s' not known in device data '%s'\n", self::$user, self::$device);
768        }
769
770        foreach ($users as $user) {
771            if (self::$user !== false && strtolower($user) !== self::$user) {
772                continue;
773            }
774            echo "Synchronized by user: ". $user. "\n";
775            self::printDeviceData(self::$device, $user);
776        }
777    }
778
779    /**
780     * Command "Wipe device"
781     * Marks a device of that user to be remotely wiped
782     *
783     * @return
784     * @access public
785     */
786    static public function CommandWipeDevice() {
787        $stat = ZPushAdmin::WipeDevice($_SERVER["LOGNAME"], self::$user, self::$device);
788
789        if (self::$user !== false && self::$device !== false) {
790            echo sprintf("Mark device '%s' of user '%s' to be wiped: %s", self::$device, self::$user, ($stat)?'OK':ZLog::GetLastMessage(LOGLEVEL_ERROR)). "\n";
791
792            if ($stat) {
793                echo "Updated information about this device:\n";
794                self::printDeviceData(self::$device, self::$user);
795            }
796        }
797        elseif (self::$user !== false) {
798            echo sprintf("Mark devices of user '%s' to be wiped: %s", self::$user, ($stat)?'OK':ZLog::GetLastMessage(LOGLEVEL_ERROR)). "\n";
799            self::CommandShowDevices();
800        }
801    }
802
803    /**
804     * Command "Remove device".
805     * Removes a device of that user from the device list.
806     *
807     * @return
808     * @access public
809     */
810    static public function CommandRemoveDevice() {
811        $stat = ZPushAdmin::RemoveDevice(self::$user, self::$device, self::$daysold, time());
812        if (self::$user === false)
813           echo sprintf("State data of device '%s' removed: %s", self::$device, ($stat)?'OK':ZLog::GetLastMessage(LOGLEVEL_ERROR)). "\n";
814        elseif (self::$device === false)
815           echo sprintf("State data of all devices of user '%s' removed: %s", self::$user, ($stat)?'OK':ZLog::GetLastMessage(LOGLEVEL_ERROR)). "\n";
816        else
817           echo sprintf("State data of device '%s' of user '%s' removed: %s", self::$device, self::$user, ($stat)?'OK':ZLog::GetLastMessage(LOGLEVEL_ERROR)). "\n";
818
819        if (ZPushAdmin::$status == ZPushAdmin::STATUS_DEVICE_SYNCED_AFTER_DAYSOLD) {
820            print("Some devices might not have been removed because of --days-old parameter. Check Z-Push log file for more details.\n");
821        }
822    }
823
824    /**
825     * Command "Resync device(s)"
826     * Resyncs one or all devices of that user
827     *
828     * @return
829     * @access public
830     */
831    static public function CommandResyncDevices() {
832        $stat = ZPushAdmin::ResyncDevice(self::$user, self::$device);
833        echo sprintf("Resync of device '%s' of user '%s': %s", self::$device, self::$user, ($stat)?'Requested':ZLog::GetLastMessage(LOGLEVEL_ERROR)). "\n";
834    }
835
836    /**
837     * Command "Resync folder(s)"
838     * Resyncs a folder type of a specific device/user or of all users
839     *
840     * @return
841     * @access public
842     */
843    static public function CommandResyncFolder() {
844        // if no device is specified, search for all devices of a user. If user is not set, all devices are returned.
845        if (self::$device === false) {
846            $devicelist = ZPushAdmin::ListDevices(self::$user);
847            if (empty($devicelist)) {
848                echo "\tno devices/users found\n";
849                return true;
850            }
851        }
852        else
853            $devicelist = array(self::$device);
854
855        foreach ($devicelist as $deviceId) {
856            $users = ZPushAdmin::ListUsers($deviceId);
857            foreach ($users as $user) {
858                if (self::$user && self::$user != $user)
859                    continue;
860                self::resyncFolder($deviceId, $user, self::$type);
861            }
862        }
863
864    }
865
866    /**
867     * Command "Resync hierarchy"
868     * Resyncs a folder type of a specific device/user or of all users
869     *
870     * @return
871     * @access public
872     */
873    static public function CommandResyncHierarchy() {
874        // if no device is specified, search for all devices of a user. If user is not set, all devices are returned.
875        if (self::$device === false) {
876            $devicelist = ZPushAdmin::ListDevices(self::$user);
877            if (empty($devicelist)) {
878                echo "\tno devices/users found\n";
879                return true;
880            }
881        }
882        else
883            $devicelist = array(self::$device);
884
885        foreach ($devicelist as $deviceId) {
886            $users = ZPushAdmin::ListUsers($deviceId);
887            foreach ($users as $user) {
888                if (self::$user && self::$user != $user)
889                    continue;
890                self::resyncHierarchy($deviceId, $user);
891            }
892        }
893
894    }
895
896    /**
897     * Command to clear the loop detection data
898     * Mobiles may enter loop detection (one-by-one synchring due to timeouts / erros).
899     *
900     * @return
901     * @access public
902     */
903    static public function CommandClearLoopDetectionData() {
904        $stat = false;
905        $stat = ZPushAdmin::ClearLoopDetectionData(self::$user, self::$device);
906        if (self::$user === false && self::$device === false)
907           echo sprintf("System wide loop detection data removed: %s", ($stat)?'OK':ZLog::GetLastMessage(LOGLEVEL_ERROR)). "\n";
908        elseif (self::$user === false)
909           echo sprintf("Loop detection data of device '%s' removed: %s", self::$device, ($stat)?'OK':ZLog::GetLastMessage(LOGLEVEL_ERROR)). "\n";
910        elseif (self::$device === false && self::$user !== false)
911           echo sprintf("Error: %s", ($stat)?'OK':ZLog::GetLastMessage(LOGLEVEL_WARN)). "\n";
912        else
913           echo sprintf("Loop detection data of device '%s' of user '%s' removed: %s", self::$device, self::$user, ($stat)?'OK':ZLog::GetLastMessage(LOGLEVEL_ERROR)). "\n";
914    }
915
916    /**
917     * Command to add a shared folder for a user.
918     *
919     * @return
920     * @access public
921     */
922    static public function CommandAddShared() {
923        // If no device is specified, search for all devices of a user.
924        if (self::$device === false) {
925            $devicelist = ZPushAdmin::ListDevices(self::$user);
926            if (empty($devicelist)) {
927                echo "\tno devices/users found\n";
928                return true;
929            }
930        }
931        else {
932            $devicelist = array(self::$device);
933        }
934        switch (self::$type) {
935            case self::TYPE_OPTION_EMAIL:
936                $type = SYNC_FOLDER_TYPE_USER_MAIL;
937                break;
938            case self::TYPE_OPTION_CALENDAR:
939                $type = SYNC_FOLDER_TYPE_USER_APPOINTMENT;
940                break;
941            case self::TYPE_OPTION_CONTACT:
942                $type = SYNC_FOLDER_TYPE_USER_CONTACT;
943                break;
944            case self::TYPE_OPTION_TASK:
945                $type = SYNC_FOLDER_TYPE_USER_TASK;
946                break;
947            case self::TYPE_OPTION_NOTE:
948                $type = SYNC_FOLDER_TYPE_USER_NOTE;
949                break;
950        }
951
952        foreach ($devicelist as $devid) {
953            $status = ZPushAdmin::AdditionalFolderAdd(self::$user, $devid, self::$store, self::$folderid, self::$foldername, $type, self::$flags);
954            if ($status) {
955                printf("Successfully added folder '%s' for user '%s' on device '%s'.\n", self::$foldername, self::$user, $devid);
956            }
957            else {
958                printf("Failed adding folder '%s' for user '%s' on device '%s'. %s.\n", self::$foldername, self::$user, $devid, ZLog::GetLastMessage(LOGLEVEL_ERROR));
959            }
960        }
961
962    }
963
964    /**
965     * Command to remove a shared folder for a user.
966     *
967     * @return
968     * @access public
969     */
970    static public function CommandRemoveShared() {
971        // if no device is specified, search for all devices of a user. If user is not set, all devices are returned.
972        if (self::$device === false) {
973            $devicelist = ZPushAdmin::ListDevices(self::$user);
974            if (empty($devicelist)) {
975                echo "\tno devices/users found\n";
976                return true;
977            }
978        }
979        else {
980            $devicelist = array(self::$device);
981        }
982
983        foreach ($devicelist as $devid) {
984            $status = ZPushAdmin::AdditionalFolderRemove(self::$user, $devid, self::$folderid);
985            if ($status) {
986                printf("Successfully removed folder with id '%s' for user '%s' on device '%s'.\n", self::$folderid, self::$user, $devid);
987            }
988            else {
989                printf("Failed removing folder with id '%s' for user '%s' on device '%s'. %s.\n", self::$folderid, self::$user, $devid, ZLog::GetLastMessage(LOGLEVEL_ERROR));
990            }
991        }
992
993    }
994
995    /**
996     * Command to list all configured shares.
997     *
998     * @access public
999     * @return void
1000     */
1001    static public function CommandListShares() {
1002        $devicelist = ZPushAdmin::ListDevices();
1003        if (empty($devicelist)) {
1004                echo "\tno devices/users found\n";
1005                return true;
1006        }
1007        $shares = array();
1008        $folderToStore = array();
1009
1010        // It's necessary to always get all users and devices and then the shares of the device
1011        // as the shares are only available in the device.
1012        foreach ($devicelist as $deviceId) {
1013            $users = ZPushAdmin::ListUsers($deviceId);
1014            foreach ($users as $user) {
1015                $device = ZPushAdmin::GetDeviceDetails($deviceId, $user);
1016                if ($device instanceof ASDevice) {
1017                    $sharedFolders = $device->GetAdditionalFolders();
1018                    if (!empty($sharedFolders)) {
1019                        foreach ($sharedFolders as $sharedFolder) {
1020                            if (!isset($sharedFolder['store'], $sharedFolder['folderid'], $sharedFolder['name'])) {
1021                                printf("User '%s' has a shared folder configured on device '%s', but store, folderid, or name of this share are not set: %s", $user, $deviceId, print_r($sharedFolder, 1));
1022                                continue;
1023                            }
1024                            $folderToStore[$sharedFolder['folderid']] = strtolower($sharedFolder['store']);
1025                            $shares[strtolower($sharedFolder['store'])][$sharedFolder['folderid']][] = array('user' => $user, 'deviceId' => $deviceId, 'name' => $sharedFolder['name']);
1026                        }
1027                    }
1028                }
1029            }
1030        }
1031
1032
1033        if (empty($shares)) {
1034            print("There currently aren't any opened shares.\n");
1035            return;
1036        }
1037
1038        if (self::$command == self::COMMAND_LISTFOLDERSHARES) {
1039            if (!isset($folderToStore[self::$folderid])) {
1040                printf("The folder with the requested id '%s' isn't currently opened by anyone.\n", self::$folderid);
1041                return;
1042            }
1043            printf("Displaying opened shares for folderid %s.\n", self::$folderid);
1044            self::printShares(array($folderToStore[self::$folderid] => array(self::$folderid => $shares[$folderToStore[self::$folderid]][self::$folderid])));
1045        }
1046        elseif (self::$command == self::COMMAND_LISTSTORESHARES) {
1047            $store = strtolower(self::$store);
1048            if (!isset($shares[$store])) {
1049                printf("None of the folders of the requested store '%s' is currently opened.\n", self::$store);
1050                return;
1051            }
1052            self::printShares(array($store => $shares[$store]));
1053        }
1054        else {
1055            print("Displaying opened shares of all users.\n");
1056            self::printShares($shares);
1057        }
1058    }
1059
1060    /**
1061     * Command to list each folder and FOLDERID of user USER and device DEVICE.
1062     *
1063     * @access public
1064     * @return void
1065     */
1066    static public function CommandListFolders() {
1067
1068        $device = ZPushAdmin::GetDeviceDetails(self::$device, self::$user, true);
1069        if (! $device instanceof ASDevice) {
1070            printf("Folder details failed: %s\n", ZLog::GetLastMessage(LOGLEVEL_ERROR));
1071            return false;
1072        }
1073
1074        echo "Folders list of DeviceId: ".self::$device."\n";
1075        echo "-----------------------------------------------------------------------\n";
1076        $folders = $device->GetAllFolderIds();
1077        $synchedFolders = 0;
1078        $notSynchedFolders = 0;
1079        $sharedFolders = 0;
1080        $hc = $device->GetHierarchyCache();
1081        echo "FolderID\t\t\t\t\tShortID\tDisplay Name\n";
1082        echo "-----------------------------------------------------------------------\n";
1083        foreach ($folders as $folderid) {
1084            if ($device->GetFolderUUID($folderid)) {
1085                $synchedFolders++;
1086                $notSynced = '';
1087            }
1088            else {
1089                $notSynchedFolders++;
1090                $notSynced = "\t"."NOT SYNCHED";
1091            }
1092            $folder = $hc->GetFolder($folderid);
1093            $name = $folder ? $folder->displayname : "unknown";
1094            if (strcmp($name, 'unknown') == 0) {
1095                echo "\t\t\t\t\t";
1096            }
1097            echo $folder->BackendId."\t".$folderid."\t".$name.$notSynced."\n";
1098            if (Utils::GetFolderOriginFromId($folderid) != DeviceManager::FLD_ORIGIN_USER) {
1099                $sharedFolders++;
1100            }
1101        }
1102        echo "\nTotal folders: ".count($folders)."\n";
1103        echo "Synchronized folders: ".$synchedFolders."\n";
1104        echo "Not synchronized folders: ".$notSynchedFolders."\n";
1105        echo "Shared/impersonated folders: ".$sharedFolders."\n";
1106        echo "Short folder Ids: ". ($device->HasFolderIdMapping() ? "Yes":"No") ."\n";
1107    }
1108
1109    /**
1110     * Resynchronizes a folder type of a device & user
1111     *
1112     * @param string    $deviceId       the id of the device
1113     * @param string    $user           the user
1114     * @param string    $type           the folder type
1115     *
1116     * @return
1117     * @access private
1118     */
1119    static private function resyncFolder($deviceId, $user, $type) {
1120        $device = ZPushAdmin::GetDeviceDetails($deviceId, $user);
1121
1122        if (! $device instanceof ASDevice) {
1123            echo sprintf("Folder resync failed: %s\n", ZLog::GetLastMessage(LOGLEVEL_ERROR));
1124            return false;
1125        }
1126
1127        $folders = array();
1128        $searchFor = $type;
1129        // get the KOE gab folderid
1130        if ($type == self::TYPE_OPTION_GAB) {
1131            if (@constant('KOE_GAB_FOLDERID') !== '') {
1132                $gab = KOE_GAB_FOLDERID;
1133            }
1134            else {
1135                $gab = $device->GetKoeGabBackendFolderId();
1136            }
1137            if (!$gab) {
1138                printf("Could not find KOE GAB folderid for device '%s' of user '%s'\n", $deviceId, $user);
1139                return false;
1140            }
1141            $searchFor = $gab;
1142        }
1143        // potential long ids are converted to folderids here, incl. the gab id
1144        $searchFor = strtolower($device->GetFolderIdForBackendId($searchFor, false, false, null));
1145
1146        foreach ($device->GetAllFolderIds() as $folderid) {
1147            // if  submitting a folderid as type to resync a specific folder.
1148            if (strtolower($folderid) === $searchFor) {
1149                printf("Found and resynching requested folderid '%s' on device '%s' of user '%s'\n", $folderid, $deviceId, $user);
1150                $folders[] = $folderid;
1151                break;
1152            }
1153
1154            if ($device->GetFolderUUID($folderid)) {
1155                $foldertype = $device->GetFolderType($folderid);
1156                switch($foldertype) {
1157                    case SYNC_FOLDER_TYPE_APPOINTMENT:
1158                    case SYNC_FOLDER_TYPE_USER_APPOINTMENT:
1159                        if ($searchFor == "calendar")
1160                            $folders[] = $folderid;
1161                        break;
1162                    case SYNC_FOLDER_TYPE_CONTACT:
1163                    case SYNC_FOLDER_TYPE_USER_CONTACT:
1164                        if ($searchFor == "contact")
1165                            $folders[] = $folderid;
1166                        break;
1167                    case SYNC_FOLDER_TYPE_TASK:
1168                    case SYNC_FOLDER_TYPE_USER_TASK:
1169                        if ($searchFor == "task")
1170                            $folders[] = $folderid;
1171                        break;
1172                    case SYNC_FOLDER_TYPE_NOTE:
1173                    case SYNC_FOLDER_TYPE_USER_NOTE:
1174                        if ($searchFor == "note")
1175                            $folders[] = $folderid;
1176                        break;
1177                    default:
1178                        if ($searchFor == "email")
1179                            $folders[] = $folderid;
1180                        break;
1181                }
1182            }
1183        }
1184
1185        $stat = ZPushAdmin::ResyncFolder($user, $deviceId, $folders);
1186        echo sprintf("Resync of %d folders of type '%s' on device '%s' of user '%s': %s\n", count($folders), $type, $deviceId, $user, ($stat)?'Requested':ZLog::GetLastMessage(LOGLEVEL_ERROR));
1187    }
1188
1189    /**
1190     * Resynchronizes the hierarchy of a device & user
1191     *
1192     * @param string    $deviceId       the id of the device
1193     * @param string    $user           the user
1194     *
1195     * @return
1196     * @access private
1197     */
1198    static private function resyncHierarchy($deviceId, $user) {
1199        $stat = ZPushAdmin::ResyncHierarchy($user, $deviceId);
1200        echo sprintf("Removing hierarchy information for resync on device '%s' of user '%s': %s\n",  $deviceId, $user, ($stat)?'Requested':ZLog::GetLastMessage(LOGLEVEL_ERROR));
1201    }
1202
1203    /**
1204     * Fixes the states for potential issues.
1205     *
1206     * @param string    $username       the user
1207     *
1208     * @return
1209     * @access private
1210     */
1211    static private function CommandFixStates($username=false) {
1212        echo "Validating and fixing states (this can take some time):\n";
1213        if(!self::$devicedriven){
1214
1215            echo "\t".date('H:i:s')." Checking username casings: ";
1216            if ($stat = ZPushAdmin::FixStatesDifferentUsernameCases($username))
1217                printf("Processed: %d - Converted: %d - Removed: %d\n", $stat[0], $stat[1], $stat[2]);
1218            else
1219                echo ZLog::GetLastMessage(LOGLEVEL_ERROR) . "\n";
1220
1221            // fixes ZP-339
1222            echo "\t".date('H:i:s')." Checking available devicedata & user linking: ";
1223            if ($stat = ZPushAdmin::FixStatesDeviceToUserLinking($username))
1224                printf("Processed: %d - Fixed: %d\n", $stat[0], $stat[1]);
1225            else
1226                echo ZLog::GetLastMessage(LOGLEVEL_ERROR) . "\n";
1227
1228            echo "\t".date('H:i:s')." Checking for unreferenced (obsolete) state files: ";
1229            if (($stat = ZPushAdmin::FixStatesUserToStatesLinking($username)) !== false)
1230                printf("Processed: %d - Deleted: %d\n",  $stat[0], $stat[1]);
1231            else
1232                echo ZLog::GetLastMessage(LOGLEVEL_ERROR) . "\n";
1233
1234            echo "\t".date('H:i:s')." Checking for hierarchy folder data state: ";
1235            if (($stat = ZPushAdmin::FixStatesHierarchyFolderData($username)) !== false)
1236                printf("Devices: %d - Processed: %d - Fixed: %d - Device+User without hierarchy: %d\n",  $stat[0], $stat[1], $stat[2], $stat[3]);
1237            else
1238                echo ZLog::GetLastMessage(LOGLEVEL_ERROR) . "\n";
1239
1240            echo "\t".date('H:i:s')." Checking flags of shared folders: ";
1241            if (($stat = ZPushAdmin::FixStatesAdditionalFolders($username)) !== false)
1242                printf("Devices: %d - Devices with additional folders: %d - Fixed: %d\n",  $stat[0], $stat[1], $stat[2]);
1243            else
1244                echo ZLog::GetLastMessage(LOGLEVEL_ERROR) . "\n";
1245        } else{
1246            //used for recording the total process time
1247            $fsStartTime= new DateTime("now");
1248            //load devices list
1249            $devices = ZPushAdmin::GetAllDevices();
1250            $devicesCount = count($devices);
1251            echo "Found ".$devicesCount." devices\r\n";
1252            $processedDevices = 0;
1253            //structure for hold stats
1254            $stats = array(
1255                // Results of ZPushAdmin::FixStatesDifferentUsernameCases
1256                array( "processed" => 0, "converted" => 0, "removed" => 0 ),
1257                // Results of ZPushAdmin::FixStatesDeviceToUserLinking
1258                array( "processed" => 0, "fixed" => 0 ),
1259                // Results of ZPushAdmin::FixStatesUserToStatesLinking
1260                array( "processed" => 0, "deleted" => 0 ),
1261                // Results of ZPushAdmin::FixStatesHierarchyFolderData
1262                array( "devices" => 0, "processed" => 0, "fixed" => 0, "noHierarchy" =>0 ),
1263                // Results of ZPushAdmin::FixStatesAdditionalFolders
1264                array( "devices" => 0, "devicesWithAddFolders" => 0, "fixed" => 0)
1265            );
1266
1267            // loop every device
1268            foreach ($devices as $devid) {
1269                $processedDevices++;
1270                if (defined('LOGFIXSTATES') && LOGFIXSTATES === true) {
1271                    echo "\tProcessing ".$processedDevices."/".$devicesCount." devices: ".$devid."\r\n";
1272                    ZLog::Write(LOGLEVEL_INFO, sprintf("FixStatesDeviceDriven(): Processing %d of %d . Device %s", $processedDevices , $devicesCount, $devid));
1273                }
1274
1275                if ($stat = ZPushAdmin::FixStatesDifferentUsernameCases(false,$devid)){
1276                    $stats[0]["processed"] += $stat[0];
1277                    $stats[0]["converted"] += $stat[1];
1278                    $stats[0]["removed"] += $stat[2];
1279                }
1280                else
1281                    echo ZLog::GetLastMessage(LOGLEVEL_ERROR) . "\n";
1282
1283                // fixes ZP-339
1284                if ($stat = ZPushAdmin::FixStatesDeviceToUserLinking(false,$devid)){
1285                    $stats[1]["processed"] += $stat[0];
1286                    $stats[1]["fixed"] += $stat[1];
1287                }
1288                else
1289                    echo ZLog::GetLastMessage(LOGLEVEL_ERROR) . "\n";
1290
1291                if (($stat = ZPushAdmin::FixStatesUserToStatesLinking(false,$devid)) !== false){
1292                    $stats[2]["processed"] += $stat[0];
1293                    $stats[2]["deleted"] += $stat[1];
1294                }
1295                else
1296                    echo ZLog::GetLastMessage(LOGLEVEL_ERROR) . "\n";
1297
1298                if (($stat = ZPushAdmin::FixStatesHierarchyFolderData(false,$devid)) !== false){
1299                    $stats[3]["devices"] += $stat[0];
1300                    $stats[3]["processed"] += $stat[1];
1301                    $stats[3]["fixed"] += $stat[2];
1302                    $stats[3]["noHierarchy"] += $stat[3];
1303                }
1304                else
1305                    echo ZLog::GetLastMessage(LOGLEVEL_ERROR) . "\n";
1306
1307                if (($stat = ZPushAdmin::FixStatesAdditionalFolders(false,$devid)) !== false){
1308                    $stats[4]["devices"] += $stat[0];
1309                    $stats[4]["devicesWithAddFolders"] += $stat[1];
1310                    $stats[4]["fixed"] += $stat[2];
1311                }
1312                else
1313                    echo ZLog::GetLastMessage(LOGLEVEL_ERROR) . "\n";
1314            }
1315            //used for recording the total process time
1316            $fsEndTime = new DateTime("now");
1317            $timeInterval = date_diff($fsStartTime, $fsEndTime)->format('%H:%I:%S');
1318            echo "Total process time: ".$timeInterval."\n"."\n";
1319            ZLog::Write(LOGLEVEL_INFO, sprintf("FixStatesDeviceDriven(): Finished. Total process time: ".$timeInterval));
1320            printf("Check username casings. Processed: %d - Converted: %d - Removed: %d\n",
1321                $stats[0]["processed"], $stats[0]["converted"], $stats[0]["removed"] );
1322            printf("Check available devicedata & user linking. Processed: %d - Fixed: %d\n",
1323                $stats[1]["processed"], $stats[1]["fixed"] );
1324            printf("Check for unreferenced (obsolete) state files. Processed: %d - Deleted: %d\n",
1325                $stats[2]["processed"], $stats[2]["deleted"] );
1326            printf("Check for hierarchy folder data state. Devices: %d - Processed: %d - Fixed: %d - Device+User without hierarchy: %d\n",
1327                $stats[3]["devices"], $stats[3]["processed"], $stats[3]["fixed"], $stats[3]["noHierarchy"] );
1328            printf("Check flags of shared folders. Devices: %d - Devices with additional folders: %d - Fixed: %d\n",
1329                $stats[4]["devices"], $stats[4]["devicesWithAddFolders"], $stats[4]["fixed"] );
1330        }
1331    }
1332
1333    /**
1334     * Prints detailed informations about a device
1335     *
1336     * @param string    $deviceId       the id of the device
1337     * @param string    $user           the user
1338     *
1339     * @return
1340     * @access private
1341     */
1342    static private function printDeviceData($deviceId, $user) {
1343        global $additionalFolders;
1344        $device = ZPushAdmin::GetDeviceDetails($deviceId, $user, true);
1345
1346        if (! $device instanceof ASDevice) {
1347            echo sprintf("Folder resync failed: %s\n", ZLog::GetLastMessage(LOGLEVEL_ERROR));
1348            return false;
1349        }
1350
1351        echo "-----------------------------------------------------\n";
1352        echo "DeviceId:\t\t$deviceId\n";
1353        echo "Device type:\t\t". ($device->GetDeviceType() !== ASDevice::UNDEFINED ? $device->GetDeviceType() : "unknown") ."\n";
1354        echo "UserAgent:\t\t".($device->GetDeviceUserAgent()!== ASDevice::UNDEFINED ? $device->GetDeviceUserAgent() : "unknown") ."\n";
1355        // TODO implement $device->GetDeviceUserAgentHistory()
1356
1357        if (!self::$shared) {
1358            // Gather some statistics about synchronized folders
1359            $folders = $device->GetAllFolderIds();
1360            $synchedFolders = 0;
1361            $synchedFolderTypes = array();
1362            $syncedFoldersInProgress = 0;
1363            $hc = $device->GetHierarchyCache();
1364            foreach ($folders as $folderid) {
1365                if ($device->GetFolderUUID($folderid)) {
1366                    $synchedFolders++;
1367                    $type = $device->GetFolderType($folderid);
1368                    $folder = $hc->GetFolder($folderid);
1369                    $name = $folder ? $folder->displayname : "unknown";
1370                    switch($type) {
1371                        case SYNC_FOLDER_TYPE_APPOINTMENT:
1372                        case SYNC_FOLDER_TYPE_USER_APPOINTMENT:
1373                            if ($name == KOE_GAB_NAME) {
1374                                $gentype = "GAB";
1375                            }
1376                            else {
1377                                $gentype = "Calendars";
1378                            }
1379                            break;
1380                        case SYNC_FOLDER_TYPE_CONTACT:
1381                        case SYNC_FOLDER_TYPE_USER_CONTACT:
1382                            $gentype = "Contacts";
1383                            break;
1384                        case SYNC_FOLDER_TYPE_TASK:
1385                        case SYNC_FOLDER_TYPE_USER_TASK:
1386                            $gentype = "Tasks";
1387                            break;
1388                        case SYNC_FOLDER_TYPE_NOTE:
1389                        case SYNC_FOLDER_TYPE_USER_NOTE:
1390                            $gentype = "Notes";
1391                            break;
1392                        default:
1393                            $gentype = "Emails";
1394                            break;
1395                    }
1396                    if (!isset($synchedFolderTypes[$gentype]))
1397                        $synchedFolderTypes[$gentype] = 0;
1398                    $synchedFolderTypes[$gentype]++;
1399
1400                    // set the folder name for all folders which are not fully synchronized yet
1401                    $fstatus = $device->GetFolderSyncStatus($folderid);
1402                    if ($fstatus !== false && is_array($fstatus)) {
1403                        $fstatus['name'] = $name ? $name : $gentype;
1404                        $device->SetFolderSyncStatus($folderid, $fstatus);
1405                        $syncedFoldersInProgress++;
1406                    }
1407                }
1408            }
1409            $folderinfo = "";
1410            foreach ($synchedFolderTypes as $gentype=>$count) {
1411                $folderinfo .= $gentype;
1412                if ($count>1) $folderinfo .= "($count)";
1413                $folderinfo .= " ";
1414            }
1415            if (!$folderinfo) $folderinfo = "None available";
1416
1417            // device information transmitted during Settings command
1418            if ($device->GetDeviceModel())
1419                echo "Device Model:\t\t". $device->GetDeviceModel(). "\n";
1420            if ($device->GetDeviceIMEI())
1421                echo "Device IMEI:\t\t". $device->GetDeviceIMEI(). "\n";
1422            if ($device->GetDeviceFriendlyName())
1423                echo "Device friendly name:\t". $device->GetDeviceFriendlyName(). "\n";
1424            if ($device->GetDeviceOS())
1425                echo "Device OS:\t\t". $device->GetDeviceOS(). "\n";
1426            if ($device->GetDeviceOSLanguage())
1427                echo "Device OS Language:\t". $device->GetDeviceOSLanguage(). "\n";
1428            if ($device->GetDevicePhoneNumber())
1429                echo "Device Phone nr:\t". $device->GetDevicePhoneNumber(). "\n";
1430            if ($device->GetDeviceMobileOperator())
1431                echo "Device Operator:\t". $device->GetDeviceMobileOperator(). "\n";
1432            if ($device->GetDeviceEnableOutboundSMS())
1433                echo "Device Outbound SMS:\t". $device->GetDeviceEnableOutboundSMS(). "\n";
1434
1435            echo "ActiveSync version:\t".($device->GetASVersion() ? $device->GetASVersion() : "unknown") ."\n";
1436            echo "First sync:\t\t". strftime("%Y-%m-%d %H:%M", $device->GetFirstSyncTime()) ."\n";
1437            echo "Last sync:\t\t". ($device->GetLastSyncTime() ? strftime("%Y-%m-%d %H:%M", $device->GetLastSyncTime()) : "never")."\n";
1438
1439
1440            $filterType = (defined('SYNC_FILTERTIME_MAX') && SYNC_FILTERTIME_MAX > SYNC_FILTERTYPE_ALL) ? SYNC_FILTERTIME_MAX : SYNC_FILTERTYPE_ALL;
1441            $maxDevice = $device->GetSyncFilterType();
1442            if ($maxDevice !== false && $maxDevice > SYNC_FILTERTYPE_ALL && ($filterType == SYNC_FILTERTYPE_ALL || $maxDevice < $filterType)) {
1443                $filterType = $maxDevice;
1444            }
1445            switch($filterType) {
1446                case SYNC_FILTERTYPE_1DAY:
1447                    $filterTypeString = "1 day back";
1448                    break;
1449                case SYNC_FILTERTYPE_3DAYS:
1450                    $filterTypeString = "3 days back";
1451                    break;
1452                case SYNC_FILTERTYPE_1WEEK:
1453                    $filterTypeString = "1 week back";
1454                    break;
1455                case SYNC_FILTERTYPE_2WEEKS:
1456                    $filterTypeString = "2 weeks back";
1457                    break;
1458                case SYNC_FILTERTYPE_1MONTH:
1459                    $filterTypeString = "1 month back";
1460                    break;
1461                case SYNC_FILTERTYPE_3MONTHS:
1462                    $filterTypeString = "3 months back";
1463                    break;
1464                case SYNC_FILTERTYPE_6MONTHS:
1465                    $filterTypeString = "6 months back";
1466                    break;
1467                default:
1468                    $filterTypeString = "unlimited";
1469            }
1470            echo "Sync Period:\t\t". $filterTypeString . " (".$filterType.")\n";
1471            echo "Total folders:\t\t". count($folders). "\n";
1472            echo "Short folder Ids:\t". ($device->HasFolderIdMapping() ? "Yes":"No") ."\n";
1473            echo "Synchronized folders:\t". $synchedFolders;
1474            if ($syncedFoldersInProgress > 0)
1475                echo " (". $syncedFoldersInProgress. " in progress)";
1476            echo "\n";
1477            echo "Synchronized data:\t$folderinfo\n";
1478            if ($syncedFoldersInProgress > 0) {
1479                echo "Synchronization progress:\n";
1480                foreach ($folders as $folderid) {
1481                    $d = $device->GetFolderSyncStatus($folderid);
1482                    if ($d) {
1483                        $status = "";
1484                        if ($d['total'] > 0) {
1485                            $percent = round($d['done']*100/$d['total']);
1486                            $status = sprintf("Status: %s%d%% (%d/%d)", ($percent < 10)?" ":"", $percent, $d['done'], $d['total']);
1487                        }
1488                        if (strlen($d['name']) > 20) {
1489                            $d['name'] = substr($d['name'], 0, 18) . "..";
1490                        }
1491                        printf("\tFolder: %s Sync: %s %s\n", str_pad($d['name'], 20), str_pad($d['status'], 13), $status);
1492                    }
1493                }
1494            }
1495        }
1496        // additional folders
1497        $addFolders = array();
1498        $sharedFolders = $device->GetAdditionalFolders();
1499        array_walk($sharedFolders, function (&$key) { $key["origin"] = 'Shared'; });
1500        // $additionalFolders comes directly from the config
1501        array_walk($additionalFolders, function (&$key) { $key["origin"] = 'Configured'; });
1502        foreach(array_merge($additionalFolders,$sharedFolders) as $df) {
1503            $df['additional'] = '';
1504            $syncfolderid = $device->GetFolderIdForBackendId($df['folderid'], false, false, null);
1505            switch($df['type']) {
1506                case SYNC_FOLDER_TYPE_USER_APPOINTMENT:
1507                    if ($df['name'] == KOE_GAB_NAME) {
1508                        $gentype = "GAB";
1509                    }
1510                    else {
1511                        $gentype = "Calendar";
1512                    }
1513                    break;
1514                case SYNC_FOLDER_TYPE_USER_CONTACT:
1515                    $gentype = "Contact";
1516                    break;
1517                case SYNC_FOLDER_TYPE_USER_TASK:
1518                    $gentype = "Task";
1519                    break;
1520                case SYNC_FOLDER_TYPE_USER_NOTE:
1521                    $gentype = "Note";
1522                    break;
1523                default:
1524                    $gentype = "Email";
1525                    break;
1526            }
1527            if ($device->GetFolderType($syncfolderid) == SYNC_FOLDER_TYPE_UNKNOWN) {
1528                $df['additional'] = "(KOE patching incomplete)";
1529            }
1530            $df['type'] = $gentype;
1531            $df['synched'] = ($device->GetFolderUUID($syncfolderid)) ? 'Active' : 'Inactive (not yet synchronized or no permissions)';
1532            $addFolders[] = $df;
1533        }
1534        $addFoldersTotal = !empty($addFolders) ? count($addFolders) : 'none';
1535        echo "Additional Folders:\t$addFoldersTotal\n";
1536        if ($addFoldersTotal != 'none') {
1537            if (!self::$shared) {
1538                print("\tFolder name                    Store          Type     Origin     Synched\n");
1539            }
1540        }
1541        foreach ($addFolders as $folder) {
1542            // Configured folders are always under root
1543            if (!isset($folder['parentid'])) $folder['parentid'] = '0';
1544
1545            if (!self::$shared) {
1546                if (strlen($folder['store']) > 14) {
1547                    $folder['store'] = substr($folder['store'], 0, 12) . "..";
1548                }
1549                if (strlen($folder['name']) > 30) {
1550                    $folder['name'] = substr($folder['name'], 0, 28) . "..";
1551                }
1552                printf("\t%s %s %s %s %s %s\n", str_pad($folder['name'], 30), str_pad($folder['store'], 14), str_pad($folder['type'], 8), str_pad($folder['origin'], 10), $folder['synched'], $folder['additional']);
1553            }
1554            else {
1555                printf("\tFolder name:\t%s\n", $folder['name']);
1556                printf("\tStore:\t\t%s\n", $folder['store']);
1557                printf("\tType:\t\t%s\n", $folder['type']);
1558                printf("\tOrigin:\t\t%s\n", $folder['origin']);
1559                printf("\tFolder id:\t%s\n", $folder['folderid']);
1560                printf("\tParent id:\t%s\n", $folder['parentid']);
1561                printf("\tSynched:\t%s\n", $folder['synched']);
1562                if (!empty($folder['additional'])) printf("\tAdditional:\t%s\n", $folder['additional']);
1563                echo "\t------------------------\n";
1564            }
1565        }
1566
1567        if (!self::$shared) {
1568            echo "Status:\t\t\t";
1569            switch ($device->GetWipeStatus()) {
1570                case SYNC_PROVISION_RWSTATUS_OK:
1571                    echo "OK\n";
1572                    break;
1573                case SYNC_PROVISION_RWSTATUS_PENDING:
1574                    echo "Pending wipe\n";
1575                    break;
1576                case SYNC_PROVISION_RWSTATUS_REQUESTED:
1577                    echo "Wipe requested on device\n";
1578                    break;
1579                case SYNC_PROVISION_RWSTATUS_WIPED:
1580                    echo "Wiped\n";
1581                    break;
1582                default:
1583                    echo "Not available\n";
1584                    break;
1585            }
1586            echo "WipeRequest on:\t\t". ($device->GetWipeRequestedOn() ? strftime("%Y-%m-%d %H:%M", $device->GetWipeRequestedOn()) : "not set")."\n";
1587            echo "WipeRequest by:\t\t". ($device->GetWipeRequestedBy() ? $device->GetWipeRequestedBy() : "not set")."\n";
1588            echo "Wiped on:\t\t". ($device->GetWipeActionOn() ? strftime("%Y-%m-%d %H:%M", $device->GetWipeActionOn()) : "not set")."\n";
1589            echo "Policy name:\t\t". ($device->GetPolicyName() ? $device->GetPolicyName() : ASDevice::DEFAULTPOLICYNAME)."\n";
1590        }
1591
1592        if ($device->GetKoeVersion()) {
1593            echo "Kopano Outlook Extension:\n";
1594            echo "\tVersion:\t". $device->GetKoeVersion() ."\n";
1595            echo "\tBuild:\t\t". $device->GetKoeBuild() ."\n";
1596            echo "\tBuild Date:\t". strftime("%Y-%m-%d %H:%M", $device->GetKoeBuildDate()) ."\n";
1597            echo "\tCapabilities:\t". (count($device->GetKoeCapabilities()) ? implode(',', $device->GetKoeCapabilities()) : 'unknown') ."\n";
1598            echo "\tLast access:\t". ($device->GetKoeLastAccess() ? strftime("%Y-%m-%d", $device->GetKoeLastAccess()) : 'unknown') ."\n";
1599        }
1600
1601        echo "Attention needed:\t";
1602
1603        if ($device->GetDeviceError()) {
1604            echo $device->GetDeviceError() ."\n";
1605        }
1606        // if KOE's access time is older than 7:01 h than the last successful sync it's probably inactive
1607        elseif ($device->GetKoeLastAccess() && $device->GetKoeLastAccess() + 25260 < $device->GetLastSyncTime()) {
1608            echo "KOE seems to be inactive on client\n";
1609        }
1610        if (!isset($device->ignoredmessages) || empty($device->ignoredmessages)) {
1611            echo "No errors known\n";
1612        }
1613        elseif (!self::$shared) {
1614            printf("%d messages need attention because they could not be synchronized\n", count($device->ignoredmessages));
1615            foreach ($device->ignoredmessages as $im) {
1616                $info = "";
1617                if (isset($im->asobject->subject))
1618                    $info .= sprintf("Subject: '%s'", $im->asobject->subject);
1619                if (isset($im->asobject->fileas))
1620                    $info .= sprintf("FileAs: '%s'", $im->asobject->fileas);
1621                if (isset($im->asobject->from))
1622                    $info .= sprintf(" - From: '%s'", $im->asobject->from);
1623                if (isset($im->asobject->starttime))
1624                    $info .= sprintf(" - On: '%s'", strftime("%Y-%m-%d %H:%M", $im->asobject->starttime));
1625                $reason = $im->reasonstring;
1626                if ($im->reasoncode == 2)
1627                    $reason = "Message was causing loop";
1628                printf("\tBroken object:\t'%s' ignored on '%s'\n", $im->asclass,  strftime("%Y-%m-%d %H:%M", $im->timestamp));
1629                printf("\tInformation:\t%s\n", $info);
1630                printf("\tReason: \t%s (%s)\n", $reason, $im->reasoncode);
1631                printf("\tItem/Parent id: %s/%s\n", $im->id, $im->folderid);
1632                echo "\n";
1633            }
1634        }
1635        else {
1636            print("There are some messages which need attention because they could not be synchronized. Run z-push-admin without -s or --shared.\n");
1637        }
1638
1639    }
1640
1641    /**
1642     * Prints information about opened shares.
1643     *
1644     * @param array $shares
1645     *
1646     * @access private
1647     * @return void
1648     */
1649    static private function printShares($shares) {
1650        $dashes = str_repeat('-', 145);
1651        foreach ($shares as $user => $userShares) {
1652            printf("Shares of user %s\n\n", $user);
1653
1654            printf("%s\n%-30s %-48s %-30s Device id\n%s", $dashes, "Foldername", "Folder id", "Username", $dashes);
1655            foreach ($userShares as $folderid => $folderShares) {
1656                foreach ($folderShares as $share) {
1657                    if (strlen($share['name']) > 26) {
1658                       $share['name'] = substr($share['name'], 0, 26) . '...';
1659                    }
1660                    printf("\n%-30s %-48s %-30s %-10s", $share['name'], $folderid, $share['user'], $share['deviceId']);
1661                }
1662            }
1663            printf("\n%s\n\n", $dashes);
1664        }
1665    }
1666}
1667