Seditio Source
Root |
./othercms/xenForo 2.2.8/src/XF/Admin/Controller/AddOn.php
<?php

namespace XF\Admin\Controller;

use
XF\Mvc\Entity\ArrayCollection;
use
XF\Mvc\ParameterBag;
use
XF\Mvc\Reply\AbstractReply;

use function
count, in_array, strlen;

class
AddOn extends AbstractController
{
    protected function
preDispatchController($action, ParameterBag $params)
    {
       
$this->assertAdminPermission('addOn');
    }

    public function
actionIndex()
    {
       
$upgradeable = [];
       
$installed = [];
       
$installable = [];
       
$legacy = [];
       
$skippable = [];
       
$unlisted = \XF::config('development')['unlistedAddOns'];

        foreach (
$this->getAddOnManager()->getAllAddOns() AS $id => $addOn)
        {
            if (isset(
$skippable[$id]) || $id == 'XF')
            {
                continue;
            }

            if (
$addOn->canUpgrade())
            {
               
$skip = $addOn->legacy_addon_id;
                if (
$skip)
                {
                   
$skippable[$skip] = $skip;
                    unset(
$legacy[$skip]);
                }
               
$upgradeable[$id] = $addOn;
            }
            else if (
$addOn->isLegacy())
            {
               
$legacy[$id] = $addOn;
            }
            else if (
$addOn->isInstalled())
            {
               
$installed[$id] = $addOn;
            }
            else if (
$addOn->canInstall() && in_array($id, $unlisted) === false)
            {
               
$installable[$id] = $addOn;
            }
        }

       
/** @var \XF\Repository\UpgradeCheck $upgradeCheckRepo */
       
$upgradeCheckRepo = $this->repository('XF:UpgradeCheck');
       
$upgradeCheck = $upgradeCheckRepo->canCheckForUpgrades() ? $upgradeCheckRepo->getLatestUpgradeCheck() : null;

       
$addOnRepo = $this->getAddOnRepo();

       
$viewParams = [
           
'upgradeable' => $upgradeable,
           
'installed' => $installed,
           
'installable' => $installable,
           
'legacy' => $legacy,
           
'total' => count($upgradeable) + count($installed) + count($installable) + count($legacy),
           
'disabled' => $addOnRepo->getDisabledAddOnsCache(),
           
'hasProcessing' => $addOnRepo->hasAddOnsBeingProcessed(),
           
'addOnDirWritable' => $addOnRepo->isAddOnDirectoryWritable(),
           
'upgradeCheck' => $upgradeCheck,
        ];
        return
$this->view('XF:AddOn\Listing', 'addon_list', $viewParams);
    }

    public function
actionIcon(ParameterBag $params)
    {
       
$addOn = $this->assertAddOnAvailable($params->addon_id_url);

        if (!
$addOn->hasIcon())
        {
            return
$this->notFound(\XF::phrase('this_add_on_does_not_have_icon_specified'));
        }

       
$this->setResponseType('raw');

       
$viewParams = [
           
'icon' => $addOn->getIconPath()
        ];
        return
$this->view('XF:AddOn\Icon', '', $viewParams);
    }

    public function
actionToggle(ParameterBag $params)
    {
       
$this->assertValidCsrfToken($this->filter('t', 'str'));

       
$addOn = $this->assertAddOnEntityExists($params->addon_id_url);
        if (!
$addOn->canEdit())
        {
            return
$this->noPermission();
        }


        if (!
$addOn->active)
        {
           
// check for add-on errors when enabling - ignoring warnings
           
list ($null, $errors) = $this->getAddOnWarningsAndErrors($this->getAddOnManager()->getById($addOn->addon_id));

            if (
$errors)
            {
                return
$this->error($errors);
            }
        }

       
$addOn->active = $addOn->active ? false : true;
       
$addOn->save();

        return
$this->redirect($this->buildLink('add-ons'));
    }

    public function
actionMassToggle()
    {
       
/** @var \XF\Repository\AddOn $addOnRepo */
       
$addOnRepo = $this->repository('XF:AddOn');

       
/** @var \XF\Mvc\Entity\ArrayCollection | \XF\Entity\AddOn[] $addOns */
       
$addOns = $addOnRepo->findAddOnsForList()
            ->
where('addon_id', '<>', 'XF')
            ->
fetch();

        if (
$this->filter('enable', 'bool'))
        {
            if (
$this->isPost())
            {
               
$toEnable = $this->filter('to_enable', 'array-str');

               
$this->app->db()->beginTransaction();

                foreach (
$toEnable AS $addOnId)
                {
                    if (!isset(
$addOns[$addOnId]))
                    {
                        continue;
                    }

                   
$addOn = $addOns[$addOnId];
                    if (!
$addOn->canEdit())
                    {
                        continue;
                    }

                   
$addOn->active = 1;
                   
$addOn->save(true, false);
                }

               
$this->app->db()->commit();

               
$addOnRepo->setDisabledAddOnsCache([]);

                return
$this->redirect($this->buildLink('add-ons'));
            }
            else
            {
               
$viewParams = [
                   
'addOns' => $addOns,
                   
'disabled' => $addOnRepo->getDisabledAddOnsCache() ?: $addOns->toArray()
                ];
                return
$this->view('XF:AddOn\MassDisable', 'addon_mass_enable', $viewParams);
            }
        }
        else
        {
           
$enabled = $addOnRepo->getEnabledAddOns();
            unset(
$enabled['XF']);

            if (
$this->isPost())
            {
               
$this->app->db()->beginTransaction();

               
$cache = [];
                foreach (
array_keys($enabled) AS $addOnId)
                {
                    if (!isset(
$addOns[$addOnId]))
                    {
                        continue;
                    }

                   
$addOn = $addOns[$addOnId];
                    if (!
$addOn->canEdit())
                    {
                        continue;
                    }

                   
$addOn->active = 0;
                   
$addOn->save(true, false);

                   
$cache[] = $addOnId;
                }

               
$this->app->db()->commit();

               
$addOnRepo->setDisabledAddOnsCache($cache);

                return
$this->redirect($this->buildLink('add-ons'));
            }
            else
            {
                if (!
$enabled)
                {
                    return
$this->error(\XF::phrase('there_currently_no_add_ons_to_disable'));
                }

               
$viewParams = [
                   
'addOns' => $addOns,
                   
'disabled' => $addOnRepo->getDisabledAddOnsCache()
                ];
                return
$this->view('XF:AddOn\MassDisable', 'addon_mass_disable', $viewParams);
            }
        }
    }

    public function
actionControls(ParameterBag $params)
    {
       
$addOn = $this->assertAddOnAvailable($params->addon_id_url);

       
$json = $addOn->getJson();
        if (!isset(
$json['options']) || $json['options'] === null)
        {
           
/** @var \XF\Repository\Option $optionRepo */
           
$optionRepo = $this->repository('XF:Option');

           
/** @var ArrayCollection $options */
           
list ($null, $options) = $optionRepo->getGroupsAndOptionsForAddOn($addOn->addon_id);

           
$hasOptions = ($options->count() > 0);
        }
        else if (isset(
$json['options']) && strlen($json['options']))
        {
           
$hasOptions = $json['options'];
        }
        else
        {
           
$hasOptions = false;
        }

       
$templates = $this->finder('XF:Template')
            ->
where('addon_id', $addOn->addon_id)
            ->
fetch()
            ->
groupBy('type');

       
$hasPublicTemplates = isset($templates['public']);
       
$hasEmailTemplates = isset($templates['email']);

        if (\
XF::$developmentMode)
        {
           
$hasAdminTemplates = isset($templates['admin']);
        }
        else
        {
           
$hasAdminTemplates = false;
        }

       
$phraseFinder = $this->finder('XF:Phrase');
       
$phrases = $phraseFinder->where('addon_id', $addOn->addon_id)->fetch();
       
$hasPhrases = ($phrases->count() > 0);

       
$viewParams = [
           
'addOn' => $addOn,

           
'hasOptions' => $hasOptions,
           
'hasPublicTemplates' => $hasPublicTemplates,
           
'hasEmailTemplates' => $hasEmailTemplates,
           
'hasAdminTemplates' => $hasAdminTemplates,
           
'hasPhrases' => $hasPhrases,

           
'style' => $this->plugin('XF:Style')->getActiveEditStyle(),
           
'masterStyle' => $this->repository('XF:Style')->getMasterStyle(),
           
'language' => $this->plugin('XF:Language')->getActiveEditLanguage()
        ];
        return
$this->view('XF:AddOn\Controls', 'addon_controls', $viewParams);
    }

    public function
actionOptions(ParameterBag $params)
    {
       
$this->assertAdminPermission('option');

       
$addOn = $this->assertAddOnAvailable($params->addon_id_url);

        list(
$groups, $options) = $this->repository('XF:Option')->getGroupsAndOptionsForAddOn($addOn->addon_id);

       
$seenOptionIds = [];
       
$groupedOptions = [];

        foreach (
$options AS $idGroup => $option)
        {
            list(
$optionId, $groupId) = explode('-', $idGroup, 2);

            if (isset(
$seenOptionIds[$optionId]))
            {
               
// ensure options which relate to multiple groups only appear once
               
continue;
            }
           
$seenOptionIds[$optionId] = true;

           
$groupedOptions[$groupId][] = $option;
        }

        foreach (
$groups AS $groupId => $group)
        {
            if (!isset(
$groupedOptions[$groupId]))
            {
               
// Remove any empty groups
               
unset($groups[$groupId]);
            }
        }

       
$viewParams = [
           
'addOn' => $addOn,
           
'groups' => $groups,
           
'groupedOptions' => $groupedOptions
       
];
        return
$this->view('XF:AddOn\Options', 'addon_options', $viewParams);
    }

    public function
actionSyncChanges(ParameterBag $params)
    {
       
$this->assertValidCsrfToken($this->filter('t', 'str'));

       
$addOn = $this->assertAddOnAvailable($params->addon_id_url);
        if (!
$addOn->hasPendingChanges() || !\XF::$debugMode)
        {
            return
$this->noPermission();
        }

       
$json = $addOn->getJson();

        if (
$addOn->version_id > $json['version_id'])
        {
            return
$this->error(\XF::phrase('downgrading_existing_add_on_is_not_supported'));
        }

       
$addOn->syncFromJson();

        return
$this->redirect($this->buildLink('add-ons'));
    }

    public function
actionRebuild(ParameterBag $params)
    {
       
$addOn = $this->assertAddOnAvailable($params->addon_id_url);
        if (!
$addOn->canRebuild())
        {
            return
$this->error(\XF::phrase('this_add_on_cannot_be_rebuilt'));
        }

        list (
$warnings, $errors) = $this->getAddOnWarningsAndErrors($addOn);

        if (
$this->isPost())
        {
           
$addOn->preRebuild();

           
$dataManager = $this->app->addOnDataManager();
           
$dataManager->enqueueImportAddOnData($addOn);

            return
$this->redirect($this->getFinalizeUrl($addOn, 'rebuild'));
        }
        else
        {
           
$viewParams = [
               
'addOn' => $addOn,
               
'warnings' => $warnings,
               
'errors' => $errors
           
];
            return
$this->view('XF:AddOn\Rebuild', 'addon_rebuild', $viewParams);
        }
    }

    public function
actionInstall(ParameterBag $params)
    {
       
$addOn = $this->assertAddOnAvailable($params->addon_id_url);
        if (!
$addOn->canInstall())
        {
            return
$this->error(\XF::phrase('this_add_on_cannot_be_installed'));
        }

        list (
$warnings, $errors) = $this->getAddOnWarningsAndErrors($addOn);

        if (
$this->isPost())
        {
           
// this applies to add-on changes as well, so we want to ensure errors here are shown and logged
           
\XF::app()->error()->setIgnorePendingUpgrade(true);

           
$input = $this->filter([
               
'_xfProcessing' => 'bool',
               
'params' => 'json-array',
               
'count' => 'uint',
               
'finished' => 'bool'
           
]);

            if (
$input['finished'])
            {
               
$dataManager = $this->app->addOnDataManager();
               
$dataManager->enqueueImportAddOnData($addOn);

                return
$this->redirect($this->getFinalizeUrl($addOn, 'install'));
            }
            else
            {
               
$setup = $addOn->getSetup();

                if (
$input['_xfProcessing'])
                {
                    if (
$setup)
                    {
                       
$setup->prepareForAction('install');
                    }

                   
$result = $setup ? $setup->install($input['params']) : null;
                }
                else
                {
                   
$result = null;
                   
$addOn->preInstall();
                }

                return
$this->displayAddOnActionStep(
                   
$addOn, $result, \XF::phrase('installing'), 'add-ons/install', $input['count']
                );
            }
        }
        else
        {
           
$viewParams = [
               
'addOn' => $addOn,
               
'warnings' => $warnings,
               
'errors' => $errors
           
];
            return
$this->view('XF:AddOn\Install', 'addon_install', $viewParams);
        }
    }

   
/**
     * @param \XF\Http\Upload[] $uploads
     *
     * @return \XF\Service\AddOnArchive\InstallBatchCreator
     */
   
protected function getBatchCreatorService(array $uploads)
    {
       
/** @var \XF\Service\AddOnArchive\InstallBatchCreator $creator */
       
$creator = $this->service('XF:AddOnArchive\InstallBatchCreator', $this->getAddOnManager());

        foreach (
$uploads AS $upload)
        {
           
$creator->addUpload($upload);
        }

        return
$creator;
    }

    public function
actionInstallFromArchive()
    {
       
$addOnRepo = $this->getAddOnRepo();

        if (!
$addOnRepo->canInstallFromArchive($error))
        {
            return
$this->error($error);
        }

        if (
$this->isPost())
        {
           
$uploads = $this->request->getFile('uploads', true, false);
            if (!
$uploads)
            {
                return
$this->error(\XF::phrase('error_occurred_while_uploading_files'));
            }

           
$creator = $this->getBatchCreatorService($uploads);

            if (!
$creator->validate($errors))
            {
                return
$this->error($errors);
            }

           
/** @var \XF\Entity\AddOnInstallBatch $addOnBatch */
           
$addOnBatch = $creator->save();

            return
$this->redirect(
               
$this->buildLink('add-ons/install-from-archive-confirm', null, ['batch_id' => $addOnBatch->batch_id])
            );
        }
        else
        {
            return
$this->view('XF:AddOn\InstallFromArchive', 'addon_install_from_archive');
        }
    }

    public function
actionInstallFromArchiveConfirm()
    {
       
$addOnRepo = $this->getAddOnRepo();

       
// If the batch is already created, then assume we had a reason to create it, so allow continuing.
        // Approach used by the core add-on upgrades.
       
if (!$addOnRepo->canInstallFromArchive($error, true))
        {
            return
$this->error($error);
        }

       
/** @var \XF\Entity\AddOnInstallBatch $batch */
       
$batch = $this->assertRecordExists('XF:AddOnInstallBatch', $this->filter('batch_id', 'uint'));

        if (
$this->isPost())
        {
           
$uniqueId = 'addOnInstallBatch' . $batch->batch_id;
           
$this->app->jobManager()->enqueueUnique($uniqueId, 'XF:AddOnInstallBatch', [
               
'batch_id' => $batch->batch_id,
               
'force_overwrite' => $this->filter('force_overwrite', 'bool')
            ]);

           
$reply = $this->redirect(
               
$this->buildLink('tools/run-job', null, [
                   
'only' => $uniqueId,
                   
'_xfRedirect' => $this->buildLink('add-ons/install-from-archive-complete', null, ['batch_id' => $batch->batch_id])
                ])
            );
           
$reply->setPageParam('skipManualJobRun', true);
            return
$reply;
        }
        else
        {
           
$viewParams = [
               
'batch' => $batch,
               
'plannedActions' => $batch->getPlannedActions()
            ];
            return
$this->view('XF:AddOn\InstallFromArchiveConfirm', 'addon_install_from_archive_confirm', $viewParams);
        }
    }

    public function
actionInstallFromArchiveComplete()
    {
       
/** @var \XF\Entity\AddOnInstallBatch $batch */
       
$batch = $this->assertRecordExists('XF:AddOnInstallBatch', $this->filter('batch_id', 'uint'));

       
$errorResults = array_filter($batch->results, function($result)
        {
            return (
$result['status'] == 'error');
        });

       
$viewParams = [
           
'batch' => $batch,
           
'hasErrors' => $errorResults ? true : false,
           
'addOns' => $this->em()->findByIds('XF:AddOn', array_keys($batch->results))
        ];
        return
$this->view('XF:AddOn\InstallFromArchiveComplete', 'addon_install_from_archive_complete', $viewParams);
    }

    public function
actionUpgrade(ParameterBag $params)
    {
       
$addOn = $this->assertAddOnAvailable($params->addon_id_url);
        if (!
$addOn->canUpgrade())
        {
            return
$this->error(\XF::phrase('this_add_on_cannot_be_upgraded'));
        }

        list (
$warnings, $errors) = $this->getAddOnWarningsAndErrors($addOn);

        if (
$this->isPost())
        {
           
// this applies to add-on changes as well, so we want to ensure errors here are shown and logged
           
\XF::app()->error()->setIgnorePendingUpgrade(true);

           
$input = $this->filter([
               
'_xfProcessing' => 'bool',
               
'params' => 'json-array',
               
'count' => 'uint',
               
'finished' => 'bool'
           
]);

            if (
$input['finished'])
            {
               
$dataManager = $this->app->addOnDataManager();
               
$dataManager->enqueueImportAddOnData($addOn);

                return
$this->redirect($this->getFinalizeUrl($addOn, 'upgrade'));
            }
            else
            {
               
$setup = $addOn->getSetup();

                if (
$input['_xfProcessing'])
                {
                    if (
$setup)
                    {
                       
$setup->prepareForAction('upgrade');
                    }

                   
$result = $setup ? $setup->upgrade($input['params']) : null;
                }
                else
                {
                   
$result = null;
                   
$addOn->preUpgrade();
                }

                return
$this->displayAddOnActionStep(
                   
$addOn, $result, \XF::phrase('upgrading'), 'add-ons/upgrade', $input['count']
                );
            }
        }
        else
        {
           
$viewParams = [
               
'addOn' => $addOn,
               
'warnings' => $warnings,
               
'errors' => $errors
           
];
            return
$this->view('XF:AddOn\Upgrade', 'addon_upgrade', $viewParams);
        }
    }

    public function
actionUninstall(ParameterBag $params)
    {
       
$addOn = $this->assertAddOnAvailable($params->addon_id_url);
        if (!
$addOn->canUninstall())
        {
            return
$this->error(\XF::phrase('this_add_on_cannot_be_uninstalled_like_files_missing'));
        }

        if (
$this->isPost())
        {
           
// this applies to add-on changes as well, so we want to ensure errors here are shown and logged
           
\XF::app()->error()->setIgnorePendingUpgrade(true);

           
$input = $this->filter([
               
'_xfProcessing' => 'bool',
               
'params' => 'json-array',
               
'count' => 'uint',
               
'finished' => 'bool'
           
]);

            if (
$input['finished'])
            {
               
$addOn->getInstalledAddOn()->delete();
                return
$this->redirect($this->getFinalizeUrl($addOn, 'uninstall'));
            }
            else
            {
               
$setup = $addOn->getSetup();

                if (
$input['_xfProcessing'])
                {
                    if (
$setup)
                    {
                       
$setup->prepareForAction('uninstall');
                    }

                   
$result = $setup ? $setup->uninstall($input['params']) : null;
                }
                else
                {
                   
$result = null;
                   
$addOn->preUninstall();
                }

                return
$this->displayAddOnActionStep(
                   
$addOn, $result, \XF::phrase('uninstalling'), 'add-ons/uninstall', $input['count']
                );
            }
        }
        else
        {
           
$viewParams = [
               
'addOn' => $addOn
           
];
            return
$this->view('XF:AddOn\Uninstall', 'addon_uninstall', $viewParams);
        }
    }

    public function
actionDeleteFiles(ParameterBag $params)
    {
       
$addOn = $this->assertAddOnAvailable($params->addon_id_url);
        if (!
$addOn->canDeleteFiles())
        {
            return
$this->error(\XF::phrase('files_for_this_add_on_cannot_be_deleted'));
        }

       
$undeletableFiles = $addOn->getUndeletableFiles();
       
$conflictingFiles = $addOn->getUndeletableConflictingFiles();

       
$addOnFiles = array_keys($addOn->getHashes());
       
$filesToDelete = array_diff($addOnFiles, $conflictingFiles);

        if (
$this->isPost())
        {
            if (
$undeletableFiles)
            {
                return
$this->error(\XF::phrase('files_for_this_add_on_cannot_be_deleted'));
            }

           
$uniqueId = 'AddOnDeleteFiles' . $addOn->getAddOnId();
           
$this->app->jobManager()->enqueueUnique($uniqueId, 'XF:AddOnDeleteFiles', [
               
'addon_id' => $addOn->getAddOnId(),
               
'addon_files' => $filesToDelete
           
]);

           
$reply = $this->redirect(
               
$this->buildLink('tools/run-job', null, [
                   
'only' => $uniqueId,
                   
'_xfRedirect' => $this->buildLink('add-ons/delete-files-complete', null, ['addon_id' => $addOn->getAddOnId(), 'title' => $addOn->title])
                ])
            );
           
$reply->setPageParam('skipManualJobRun', true);
            return
$reply;
        }
        else
        {
           
$viewParams = [
               
'addOn' => $addOn,
               
'filesToDelete' => $filesToDelete,
               
'undeletableFiles' => $undeletableFiles,
               
'conflictingFiles' => $conflictingFiles,
            ];
            return
$this->view('XF:Addon\DeleteFiles', 'addon_delete_files', $viewParams);
        }
    }

    protected function
getFinalizeUrl(\XF\AddOn\AddOn $addOn, $action)
    {
        return
$this->buildLink('add-ons/finalize', $addOn, [
           
't' => $this->app['csrf.token'],
           
'a' => $action
       
]);
    }

    public function
actionFinalize(ParameterBag $params)
    {
       
$this->assertValidCsrfToken($this->filter('t', 'str'));

       
$addOn = $this->getAddOnIfAvailable($params->addon_id_url);
       
$action = $this->filter('a', 'str');

       
// TODO: check whether the the import job is still enqueued. If so, it won't have completed successfully.

       
if ($addOn)
        {
           
$installed = $addOn->getInstalledAddOn();
        }
        else
        {
           
$installed = null; // legacy add-ons being uninstalled won't have an AddOn class or entity
       
}

        if (
$installed && $action != 'uninstall')
        {
           
// this is a sanity check, it shouldn't happen
           
$installed->is_processing = false;
           
$installed->save();
        }

       
$redirect = $this->buildLink('add-ons');
       
$stateChanges = [];

        if (
$addOn)
        {
            switch (
$action)
            {
                case
'upgrade':
                   
$addOn->postUpgrade($stateChanges);
                    break;
                case
'install':
                   
$addOn->postInstall($stateChanges);
                    break;
                case
'uninstall':
                   
$addOn->postUninstall();
                    break;
                case
'rebuild':
                   
$addOn->postRebuild();
                    break;
            }
        }

        if (!empty(
$stateChanges['redirect']))
        {
           
$redirect = $stateChanges['redirect'];
        }

        return
$this->redirect($redirect);
    }

    protected function
displayAddOnActionStep(
        \
XF\AddOn\AddOn $addOn,
        \
XF\AddOn\StepResult $result = null,
       
$actionText, $actionRoute, $count = 0
   
)
    {
       
$isProcessing = $this->filter('_xfProcessing', 'bool');

        if (!
$result)
        {
            if (
$isProcessing)
            {
               
$finished = true;
            }
            else
            {
               
$finished = false;
               
$isProcessing = true;
            }

           
$params = [];
        }
        else
        {
           
$finished = false;
           
$params = $result->params;
           
$params['step'] = $result->step;
            if (
$result->version)
            {
               
$params['version_id'] = $result->version;
            }
        }

       
$viewParams = [
           
'addOn' => $addOn,
           
'actionText' => $actionText,
           
'actionRoute' => $actionRoute,

           
'isProcessing' => $isProcessing,

           
'finished' => $finished,
           
'params' => $params,
           
'count' => $count + 1
       
];
        return
$this->view('XF:AddOn\RunStep', 'addon_run_step', $viewParams);
    }

    protected function
getAddOnWarningsAndErrors(\XF\AddOn\AddOn $addOn)
    {
        \
XF\Util\Php::resetOpcache();

       
$addOn->checkRequirements($errors, $warnings);
       
$addOn->passesHealthCheck($missing, $inconsistent);

       
$devMode = \XF::$developmentMode;

        if (
$missing)
        {
            if (
count($missing) > 5)
            {
               
$errors[] = \XF::phrase('this_add_on_cannot_be_installed_because_x_files_are_missing', ['missing' => count($missing)]);
            }
            else
            {
               
$errors[] = \XF::phrase('this_add_on_cannot_be_installed_because_following_files_are_missing_x', ['missing' => implode(', ', $missing)]);
            }
        }
        if (
$inconsistent)
        {
            if (
count($inconsistent) > 5)
            {
               
$warnings[] = \XF::phrase('this_add_on_contains_x_files_which_have_unexpected_contents', ['inconsistent' => count($inconsistent)]);
            }
            else
            {
               
$warnings[] = \XF::phrase('this_add_on_contains_following_files_which_have_unexpected_contents_x', ['inconsistent' => implode(', ', $inconsistent)]);
            }
        }
        if (!
$addOn->hasHashes() && !$devMode)
        {
           
$warnings[] = \XF::phrase('hashes_file_for_this_add_on_missing');
        }
        if (
$addOn->isDevOutputAvailable() && $devMode)
        {
           
$warnings[] = \XF::phrase('development_mode_is_enabled_and_dev_output_is_available');
        }
        else if (\
XF::$debugMode && !$devMode)
        {
           
$warnings[] = \XF::phrase('debug_mode_is_enabled_changes_will_be_overwritten');
        }

        return [
$warnings, $errors];
    }

   
/**
     * @param string $id
     * @param array|string|null $with
     * @param null|string $phraseKey
     *
     * @return \XF\Entity\AddOn
     */
   
protected function assertAddOnEntityExists($id, $with = null, $phraseKey = null)
    {
       
$id = $this->getAddOnRepo()->convertAddOnIdUrlVersionToBase($id);

       
/** @var \XF\Entity\AddOn $addOnEnt */
       
$addOnEnt = $this->assertRecordExists('XF:AddOn', $id, $with, $phraseKey);
        return
$addOnEnt;
    }

   
/**
     * @param string $id
     *
     * @return \XF\AddOn\AddOn
     */
   
protected function assertAddOnAvailable($id)
    {
       
$id = $this->getAddOnRepo()->convertAddOnIdUrlVersionToBase($id);

       
$addOn = $this->getAddOnManager()->getById($id);
        if (!
$addOn)
        {
            throw
$this->exception($this->error(\XF::phrase('requested_page_not_found'), 404));
        }

        return
$addOn;
    }

   
/**
     * @param string $id
     *
     * @return \XF\AddOn\AddOn|null
     */
   
protected function getAddOnIfAvailable($id)
    {
       
$id = $this->getAddOnRepo()->convertAddOnIdUrlVersionToBase($id);

        return
$this->getAddOnManager()->getById($id);
    }

   
/**
     * @return \XF\AddOn\Manager
     */
   
protected function getAddOnManager()
    {
        return
$this->app->addOnManager();
    }

   
/**
     * @return \XF\Repository\AddOn
     */
   
protected function getAddOnRepo()
    {
        return
$this->repository('XF:AddOn');
    }

    protected function
postDispatchController($action, ParameterBag $params, AbstractReply &$reply)
    {
       
$reply->setPageParam('breadcrumbPath', 'addOns');
    }
}