src/AppBundle/Twig/WebExtension.php line 647

Open in your IDE?
  1. <?php
  2. namespace AppBundle\Twig;
  3. use ApiBundle\Api\Util\AssetHelper;
  4. use AppBundle\Common\ArrayToolkit;
  5. use AppBundle\Common\ConvertIpToolkit;
  6. use AppBundle\Common\DeviceToolkit;
  7. use AppBundle\Common\ExtensionManager;
  8. use AppBundle\Common\FileToolkit;
  9. use AppBundle\Common\LoginToolkit;
  10. use AppBundle\Common\MathToolkit;
  11. use AppBundle\Common\NumberToolkit;
  12. use AppBundle\Common\PluginVersionToolkit;
  13. use AppBundle\Common\SimpleValidator;
  14. use AppBundle\Component\DeviceDetector\DeviceDetectorAdapter;
  15. use AppBundle\Component\ShareSdk\WeixinShare;
  16. use AppBundle\Util\CategoryBuilder;
  17. use AppBundle\Util\CdnUrl;
  18. use AppBundle\Util\UploadToken;
  19. use Biz\Account\Service\AccountProxyService;
  20. use Biz\CloudPlatform\CloudAPIFactory;
  21. use Biz\CloudPlatform\Service\ResourceFacadeService;
  22. use Biz\Lms\Service\LmsService;
  23. use Biz\Lms\util\KnowledgeUrlBuilder;
  24. use Biz\Gather\Service\GatherService;
  25. use Biz\ReviewCenter\Service\ReviewCenterService;
  26. use Biz\Taxonomy\Service\TagService;
  27. use Biz\User\Service\TokenService;
  28. use Codeages\Biz\Framework\Context\Biz;
  29. use Codeages\Biz\Framework\Service\Exception\ServiceException;
  30. use Common\CloudSchoolLevelConstant;
  31. use Common\CloudSchoolLevelHelper;
  32. use CorporateTrainingBundle\Biz\User\Service\UserService;
  33. use Symfony\Component\DependencyInjection\ContainerInterface;
  34. use Topxia\Service\Common\ServiceKernel;
  35. use Twig\Extension\AbstractExtension;
  36. class WebExtension extends AbstractExtension
  37. {
  38.     /**
  39.      * @var ContainerInterface
  40.      */
  41.     protected $container;
  42.     /**
  43.      * @var Biz
  44.      */
  45.     protected $biz;
  46.     protected $pageScripts;
  47.     protected $locale;
  48.     protected $defaultCloudSdkHost;
  49.     public function __construct($container, Biz $biz)
  50.     {
  51.         $this->container = $container;
  52.         $this->biz = $biz;
  53.     }
  54.     public function getFilters()
  55.     {
  56.         return [
  57.             new \Twig\TwigFilter('smart_time', [$this, 'smarttimeFilter']),
  58.             new \Twig\TwigFilter('date_format', [$this, 'dateformatFilter']),
  59.             new \Twig\TwigFilter('time_range', [$this, 'timeRangeFilter']),
  60.             new \Twig\TwigFilter('time_diff', [$this, 'timeDiffFilter']),
  61.             new \Twig\TwigFilter('remain_time', [$this, 'remainTimeFilter']),
  62.             new \Twig\TwigFilter('time_formatter', [$this, 'timeFormatterFilter']),
  63.             new \Twig\TwigFilter('location_text', [$this, 'locationTextFilter']),
  64.             new \Twig\TwigFilter('tags_html', [$this, 'tagsHtmlFilter'], ['is_safe' => ['html']]),
  65.             new \Twig\TwigFilter('file_size', [$this, 'fileSizeFilter']),
  66.             new \Twig\TwigFilter('plain_text', [$this, 'plainTextFilter'], ['is_safe' => ['html']]),
  67.             new \Twig\TwigFilter('sub_text', [$this, 'subTextFilter'], ['is_safe' => ['html']]),
  68.             new \Twig\TwigFilter('mb_substr', [$this, 'mb_substr'], ['is_safe' => ['html']]),
  69.             new \Twig\TwigFilter('duration', [$this, 'durationFilter']),
  70.             new \Twig\TwigFilter('duration_text', [$this, 'durationTextFilter']),
  71.             new \Twig\TwigFilter('format_ms_hms', [$this, 'formatMsToHmsFilter']),
  72.             new \Twig\TwigFilter('tags_join', [$this, 'tagsJoinFilter']),
  73.             new \Twig\TwigFilter('navigation_url', [$this, 'navigationUrlFilter']),
  74.             new \Twig\TwigFilter('chr', [$this, 'chrFilter']),
  75.             new \Twig\TwigFilter('bbCode2Html', [$this, 'bbCode2HtmlFilter']),
  76.             new \Twig\TwigFilter('score_text', [$this, 'scoreTextFilter']),
  77.             new \Twig\TwigFilter('simple_template', [$this, 'simpleTemplateFilter']),
  78.             new \Twig\TwigFilter('fill_question_stem_text', [$this, 'fillQuestionStemTextFilter']),
  79.             new \Twig\TwigFilter('fill_question_stem_html', [$this, 'fillQuestionStemHtmlFilter']),
  80.             new \Twig\TwigFilter('get_course_id', [$this, 'getCourseidFilter']),
  81.             new \Twig\TwigFilter('purify_html', [$this, 'getPurifyHtml']),
  82.             new \Twig\TwigFilter('purify_and_trim_html', [$this, 'getPurifyAndTrimHtml']),
  83.             new \Twig\TwigFilter('file_type', [$this, 'getFileType']),
  84.             new \Twig\TwigFilter('at', [$this, 'atFilter']),
  85.             new \Twig\TwigFilter('copyright_less', [$this, 'removeCopyright']),
  86.             new \Twig\TwigFilter('array_merge', [$this, 'arrayMerge']),
  87.             new \Twig\TwigFilter('space2nbsp', [$this, 'spaceToNbsp']),
  88.             new \Twig\TwigFilter('number_to_human', [$this, 'numberFilter']),
  89.             new \Twig\TwigFilter('array_column', [$this, 'arrayColumn']),
  90.             new \Twig\TwigFilter('rename_locale', [$this, 'renameLocale']),
  91.             new \Twig\TwigFilter('cdn', [$this, 'cdn']),
  92.             new \Twig\TwigFilter('wrap', [$this, 'wrap']),
  93.             new \Twig\TwigFilter('convert_absolute_url', [$this, 'convertAbsoluteUrl']),
  94.             new \Twig\TwigFilter('days_of_week', [$this, 'daysOfWeek']),
  95.             new \Twig\TwigFilter('filter_keys', [$this, 'filterArrayKeys']),
  96.             new \Twig\TwigFilter('exam_score', [$this, 'examScore']),
  97.             new \Twig\TwigFilter('learn_time_format', [$this, 'formatLearnTime']),
  98.             new \Twig\TwigFilter('group_by', [$this, 'groupBy']),
  99.         ];
  100.     }
  101.     public function getFunctions()
  102.     {
  103.         return [
  104.             new \Twig\TwigFunction('theme_global_script', [$this, 'getThemeGlobalScript']),
  105.             new \Twig\TwigFunction('file_uri_parse', [$this, 'parseFileUri']),
  106.             // file_path 即将废弃,不要再使用
  107.             new \Twig\TwigFunction('file_path', [$this, 'getFilePath']),
  108.             // default_path 即将废弃,不要再使用
  109.             new \Twig\TwigFunction('default_path', [$this, 'getDefaultPath']),
  110.             // file_url 即将废弃,不要再使用
  111.             new \Twig\TwigFunction('file_url', [$this, 'getFileUrl']),
  112.             // system_default_path,即将废弃,不要再使用
  113.             new \Twig\TwigFunction('system_default_path', [$this, 'getSystemDefaultPath']),
  114.             new \Twig\TwigFunction('fileurl', [$this, 'getFurl']),
  115.             new \Twig\TwigFunction('filepath', [$this, 'getFpath']),
  116.             new \Twig\TwigFunction('lazy_img', [$this, 'makeLazyImg'], ['is_safe' => ['html']]),
  117.             new \Twig\TwigFunction('avatar_path', [$this, 'avatarPath']),
  118.             new \Twig\TwigFunction('object_load', [$this, 'loadObject']),
  119.             new \Twig\TwigFunction('setting', [$this, 'getSetting']),
  120.             new \Twig\TwigFunction('set_price', [$this, 'getSetPrice']),
  121.             new \Twig\TwigFunction('percent', [$this, 'calculatePercent']),
  122.             new \Twig\TwigFunction('rate_format', [$this, 'rateFormat']),
  123.             new \Twig\TwigFunction('category_choices', [$this, 'getCategoryChoices']),
  124.             new \Twig\TwigFunction('category_choices_with_category_empty', [$this, 'getCategoryChoicesWithCategoryEmpty']),
  125.             new \Twig\TwigFunction('upload_max_filesize', [$this, 'getUploadMaxFilesize']),
  126.             new \Twig\TwigFunction('js_paths', [$this, 'getJsPaths']),
  127.             new \Twig\TwigFunction('is_plugin_installed', [$this, 'isPluginInstalled']),
  128.             new \Twig\TwigFunction('plugin_version', [$this, 'getPluginVersion']),
  129.             new \Twig\TwigFunction('version_compare', [$this, 'versionCompare']),
  130.             new \Twig\TwigFunction('is_exist_in_subarray_by_id', [$this, 'isExistInSubArrayById']),
  131.             new \Twig\TwigFunction('context_value', [$this, 'getContextValue']),
  132.             new \Twig\TwigFunction('is_feature_enabled', [$this, 'isFeatureEnabled']),
  133.             new \Twig\TwigFunction('parameter', [$this, 'getParameter']),
  134.             new \Twig\TwigFunction('upload_token', [$this, 'makeUpoadToken']),
  135.             new \Twig\TwigFunction('countdown_time', [$this, 'getCountdownTime']),
  136.             new \Twig\TwigFunction('can_show_classroom', [$this, 'canShowClassRoom']),
  137.             new \Twig\TwigFunction('can_show_article_group', [$this, 'canShowArticleGroup']),
  138.             new \Twig\TwigFunction('can_show_article', [$this, 'canShowArticle']),
  139.             new \Twig\TwigFunction('can_show_mobile_app', [$this, 'canShowMobileApp']),
  140.             new \Twig\TwigFunction('can_show_offline_activity', [$this, 'canShowOfflineActivity']),
  141.             //todo covertIP 要删除
  142.             new \Twig\TwigFunction('convertIP', [$this, 'getConvertIP']),
  143.             new \Twig\TwigFunction('convert_ip', [$this, 'getConvertIP']),
  144.             new \Twig\TwigFunction('new_convert_ip', [$this, 'getNewConverIP']),
  145.             new \Twig\TwigFunction('isHide', [$this, 'isHideThread']),
  146.             new \Twig\TwigFunction('user_coin_amount', [$this, 'userCoinAmount']),
  147.             new \Twig\TwigFunction('user_balance', [$this, 'getBalance']),
  148.             new \Twig\TwigFunction('blur_user_name', [$this, 'blurUserName']),
  149.             new \Twig\TwigFunction('blur_phone_number', [$this, 'blur_phone_number']),
  150.             new \Twig\TwigFunction('blur_idcard_number', [$this, 'blur_idcard_number']),
  151.             new \Twig\TwigFunction('blur_number', [$this, 'blur_number']),
  152.             new \Twig\TwigFunction('sub_str', [$this, 'subStr']),
  153.             new \Twig\TwigFunction('convert_encoding_sub_str', [$this, 'convertEncodingSubStr']),
  154.             new \Twig\TwigFunction('load_script', [$this, 'loadScript']),
  155.             new \Twig\TwigFunction('export_scripts', [$this, 'exportScripts']),
  156.             new \Twig\TwigFunction('order_payment', [$this, 'getOrderPayment']),
  157.             new \Twig\TwigFunction('classroom_permit', [$this, 'isPermitRole']),
  158.             new \Twig\TwigFunction('crontab_next_executed_time', [$this, 'getNextExecutedTime']),
  159.             new \Twig\TwigFunction('finger_print', [$this, 'getFingerprint']),
  160.             new \Twig\TwigFunction('get_parameters_from_url', [$this, 'getParametersFromUrl']),
  161.             new \Twig\TwigFunction('is_trial', [$this, 'isTrial']),
  162.             new \Twig\TwigFunction('timestamp', [$this, 'timestamp']),
  163.             new \Twig\TwigFunction('get_user_vip_level', [$this, 'getUserVipLevel']),
  164.             new \Twig\TwigFunction('is_without_network', [$this, 'isWithoutNetwork']),
  165.             new \Twig\TwigFunction('get_admin_roles', [$this, 'getAdminRoles']),
  166.             new \Twig\TwigFunction('render_notification', [$this, 'renderNotification']),
  167.             new \Twig\TwigFunction('route_exsit', [$this, 'routeExists']),
  168.             new \Twig\TwigFunction('is_micro_messenger', [$this, 'isMicroMessenger']),
  169.             new \Twig\TwigFunction('wx_js_sdk_config', [$this, 'weixinConfig']),
  170.             new \Twig\TwigFunction('plugin_update_notify', [$this, 'pluginUpdateNotify']),
  171.             new \Twig\TwigFunction('tag_equal', [$this, 'tagEqual']),
  172.             new \Twig\TwigFunction('get_tag', [$this, 'getTag']),
  173.             new \Twig\TwigFunction('array_index', [$this, 'arrayIndex']),
  174.             new \Twig\TwigFunction('cdn', [$this, 'getCdn']),
  175.             new \Twig\TwigFunction('is_show_mobile_page', [$this, 'isShowMobilePage']),
  176.             new \Twig\TwigFunction('is_mobile_client', [$this, 'isMobileClient']),
  177.             new \Twig\TwigFunction('is_allow_browse', [$this, 'isAllowBrowse']),
  178.             new \Twig\TwigFunction('is_ES_copyright', [$this, 'isESCopyright']),
  179.             new \Twig\TwigFunction('get_classroom_name', [$this, 'getClassroomName']),
  180.             new \Twig\TwigFunction('pop_reward_point_notify', [$this, 'popRewardPointNotify']),
  181.             new \Twig\TwigFunction('array_filter', [$this, 'arrayFilter']),
  182.             new \Twig\TwigFunction('base_path', [$this, 'basePath']),
  183.             new \Twig\TwigFunction('get_login_email_address', [$this, 'getLoginEmailAddress']),
  184.             new \Twig\TwigFunction('cloud_sdk_url', [$this, 'getCloudSdkUrl']),
  185.             new \Twig\TwigFunction('open_cloud_url', [$this, 'getOpenCloudUrl']),
  186.             new \Twig\TwigFunction('math_format', [$this, 'mathFormat']),
  187.             new \Twig\TwigFunction('parse_user_agent', [$this, 'parseUserAgent']),
  188.             new \Twig\TwigFunction('wechat_login_bind_enabled', [$this, 'isWechatLoginBind']),
  189.             new \Twig\TwigFunction('can_send_message', [$this, 'canSendMessage']),
  190.             new \Twig\TwigFunction('is_hidden_video_header', [$this, 'isHiddenVideoHeader']),
  191.             new \Twig\TwigFunction('arrays_key_convert', [$this, 'arraysKeyConvert']),
  192.             new \Twig\TwigFunction('make_local_media_file_token', [$this, 'makeLocalMediaFileToken']),
  193.             new \Twig\TwigFunction('online_advisory_auth_info', [$this, 'onlineAdvisoryAuthInfo']),
  194.             new \Twig\TwigFunction('question_html_filter', [$this, 'questionHtmlFilter']),
  195.             new \Twig\TwigFunction('uniqid', [$this, 'uniqid']),
  196.             new \Twig\TwigFunction('can_operate_question', [$this, 'canOperateQuestion']),
  197.             new \Twig\TwigFunction('sync_mode_dict', [$this, 'getSyncModeDict']),
  198.             new \Twig\TwigFunction('show_feedback_bar', [$this, 'showFeedbackBar']),
  199.             new \Twig\TwigFunction('is_saas', [$this, 'isSaaS']),
  200.             new \Twig\TwigFunction('member_deadline', [$this, 'handleDeadline']),
  201.             new \Twig\TwigFunction('get_course_market_url', [$this, 'getCourseMarketUrl']),
  202.             new \Twig\TwigFunction('org_msg', [$this, 'getOrgMsg']),
  203.             new \Twig\TwigFunction('get_category_name', [$this, 'getCategoryName']),
  204.             new \Twig\TwigFunction('get_category_ids_by_categories', [$this, 'getCategoryIdsByCategories']),
  205.             new \Twig\TwigFunction('get_review_pending_total_num', [$this, 'getReviewPendingTotalNum']),
  206.             new \Twig\TwigFunction('get_remember_me_deadline_range_text', [$this, 'getRememberMeDeadlineRangeText']),
  207.             new \Twig\TwigFunction('is_question_answered', [$this, 'isQuestionAnswered']),
  208.             new \Twig\TwigFunction('is_enabled_lms', [$this, 'isEnabledLms']),
  209.             new \Twig\TwigFunction('is_lms_version_supported', [$this, 'isLmsVersionSupported']),
  210.             new \Twig\TwigFunction('knowledge_href', [$this, 'knowledgeHref']),
  211.             new \Twig\TwigFunction('main_request_route', [$this, 'mainRequestRoute']),
  212.             new \Twig\TwigFunction('has_live_tasks', [$this, 'hasLiveTasks']),
  213.             new \Twig\TwigFunction('is_ai_coach_tenant', [$this, 'isAiCoachTenant'])
  214.         ];
  215.     }
  216.     public function getCourseMarketUrl()
  217.     {
  218.         return ServiceKernel::instance()->getParameter('security.course_market.url');
  219.     }
  220.     public function getOpenCloudUrl($path = '')
  221.     {
  222.         return $this->createService('CloudPlatform:AppService')->getOpenCloudUrl($path);
  223.     }
  224.     public function parseUserAgent($userAgent)
  225.     {
  226.         $deviceDetector = new DeviceDetectorAdapter($userAgent);
  227.         return [
  228.             'device' => $deviceDetector->getDevice(),
  229.             'client' => $deviceDetector->getClient(),
  230.             'os' => $deviceDetector->getOs(),
  231.         ];
  232.     }
  233.     public function handleDeadline($deadline, $issueDate = 0)
  234.     {
  235.         if (0 == $deadline) {
  236.             return 0;
  237.         }
  238.         if (0 == $issueDate) {
  239.             $time = time();
  240.         } else {
  241.             $time = $issueDate;
  242.         }
  243.         if (($deadline - $time) < 0) {
  244.             return -1;
  245.         }
  246.         return floor(($deadline - $time) / 3600 / 24);
  247.     }
  248.     public function arrayFilter($data, $filterName)
  249.     {
  250.         if (empty($data) || !is_array($data)) {
  251.             return [];
  252.         }
  253.         return array_filter($data, function ($value) use ($filterName) {
  254.             foreach ($filterName as $name) {
  255.                 if ('' === $value[$name]) {
  256.                     return false;
  257.                 }
  258.             }
  259.             return true;
  260.         });
  261.     }
  262.     public function getReviewPendingTotalNum()
  263.     {
  264.         $user = $this->getUserService()->getCurrentUser();
  265.         $reviewPendingCount = $this->getReviewCenterService()->getPendingTabbar($user['id']);
  266.         return array_sum(array_column($reviewPendingCount, 'pendingCount'));
  267.     }
  268.     public function isShowMobilePage()
  269.     {
  270.         $wapSetting = $this->getSetting('wap', []);
  271.         if (empty($wapSetting['enabled'])) {
  272.             return false;
  273.         }
  274.         $pcVersion = $this->container->get('request_stack')->getMainRequest()->cookies->get('PCVersion', 0);
  275.         if ($pcVersion) {
  276.             return false;
  277.         }
  278.         return DeviceToolkit::isMobileClient();
  279.     }
  280.     public function isMobileClient()
  281.     {
  282.         return DeviceToolkit::isMobileClient();
  283.     }
  284.     public function isAllowBrowse()
  285.     {
  286.         $userAgent = $this->container->get('request_stack')->getMainRequest()->headers->get('User-Agent');
  287.         $allowedBrowse = ['MicroMessenger', 'wxwork', 'DingTalk', 'Feishu'];
  288.         $allow = false;
  289.         foreach ($allowedBrowse as $browse) {
  290.             if (false !== strpos($userAgent, $browse)) {
  291.                 $allow = true;
  292.                 break;
  293.             }
  294.         }
  295.         return $allow;
  296.     }
  297.     public function isESCopyright()
  298.     {
  299.         $copyright = $this->getSetting('copyright');
  300.         $request = $this->container->get('request_stack')->getMainRequest();
  301.         $host = $request->getHttpHost();
  302.         if ($copyright) {
  303.             $result = !(
  304.                 isset($copyright['owned'])
  305.                 && isset($copyright['thirdCopyright'])
  306.                 && 2 != $copyright['thirdCopyright']
  307.                 && isset($copyright['licenseDomains'])
  308.                 && in_array($host, explode(';', $copyright['licenseDomains']))
  309.                 || (isset($copyright['thirdCopyright']) && 2 == $copyright['thirdCopyright'])
  310.             );
  311.             return $result;
  312.         }
  313.         return true;
  314.     }
  315.     public function getClassroomName()
  316.     {
  317.         return $this->getSetting('classroom.name', $this->container->get('translator')->trans('site.default.classroom'));
  318.     }
  319.     public function tagEqual($tags, $targetTagId, $targetTagGroupId)
  320.     {
  321.         foreach ($tags as $groupId => $tagId) {
  322.             if ($groupId == $targetTagGroupId && $tagId == $targetTagId) {
  323.                 return true;
  324.             }
  325.         }
  326.         return false;
  327.     }
  328.     public function getTag($tagId)
  329.     {
  330.         if (empty($tagId)) {
  331.             return null;
  332.         }
  333.         return $this->getTagService()->getTag($tagId);
  334.     }
  335.     public function arrayIndex($array, $key)
  336.     {
  337.         if (empty($array) || !is_array($array)) {
  338.             return [];
  339.         }
  340.         return ArrayToolkit::index($array, $key);
  341.     }
  342.     public function timeFormatterFilter($time)
  343.     {
  344.         $hour = floor($time / 3600);
  345.         $minute = floor(($time % 3600) / 60);
  346.         $second = $time % 60;
  347.         if ($hour > 0) {
  348.             if ($minute > 0 && $second > 0) {
  349.                 return $this->trans('site.twig.extension.time_interval.hour_minute_second', [
  350.                     '%diff_hour%' => $hour,
  351.                     '%diff_minute%' => $minute,
  352.                     '%diff_second%' => $second,
  353.                 ]);
  354.             }
  355.             if ($minute > 0) {
  356.                 return $this->trans('site.twig.extension.time_interval.hour_minute', [
  357.                     '%diff_hour%' => $hour,
  358.                     '%diff_minute%' => $minute,
  359.                 ]);
  360.             }
  361.             if ($second > 0) {
  362.                 return $this->trans('site.twig.extension.time_interval.hour_second', [
  363.                     '%diff_hour%' => $hour,
  364.                     '%diff_second%' => $second,
  365.                 ]);
  366.             }
  367.             return $this->trans('site.twig.extension.time_interval.hour', [
  368.                 '%diff%' => $hour,
  369.             ]);
  370.         }
  371.         if ($minute > 0) {
  372.             if ($second > 0) {
  373.                 return $this->trans('site.twig.extension.time_interval.minute_second', [
  374.                     '%diff_minute%' => $minute,
  375.                     '%diff_second%' => $second,
  376.                 ]);
  377.             }
  378.             return $this->trans('site.twig.extension.time_interval.minute', [
  379.                 '%diff%' => $minute,
  380.             ]);
  381.         }
  382.         return $this->trans('site.twig.extension.time_interval.second', [
  383.             '%diff%' => $second,
  384.         ]);
  385.     }
  386.     public function pluginUpdateNotify()
  387.     {
  388.         $count = $this->getAppService()->findAppCount();
  389.         $apps = $this->getAppService()->findApps(0, $count);
  390.         $apps = array_filter($apps, function ($app) {
  391.             return 'EduSoho官方' == $app['developerName'];
  392.         });
  393.         $notifies = array_reduce(
  394.             $apps,
  395.             function ($notifies, $app) {
  396.                 if (!PluginVersionToolkit::dependencyVersion($app['code'], $app['version'])) {
  397.                     $notifies[$app['type']][] = $app['name'];
  398.                 } elseif ('TRAININGMAIN' !== $app['code'] && $app['protocol'] < 3) {
  399.                     $notifies[$app['type']][] = $app['name'];
  400.                 }
  401.                 return $notifies;
  402.             },
  403.             []
  404.         );
  405.         return $notifies;
  406.     }
  407.     public function getAdminRoles()
  408.     {
  409.         return $this->createService('Role:RoleService')->searchRoles([], 'created', 0, 1000);
  410.     }
  411.     public function getCdn($type = 'default')
  412.     {
  413.         $cdn = new CdnUrl();
  414.         $cdnUrl = $cdn->get($type);
  415.         return $cdnUrl;
  416.     }
  417.     public function cdn($content)
  418.     {
  419.         $cdn = new CdnUrl();
  420.         $cdnUrl = $cdn->get('content');
  421.         if ($cdnUrl) {
  422.             $publicUrlPath = $this->container->getParameter('topxia.upload.public_url_path');
  423.             $themeUrlPath = $this->container->getParameter('topxia.web_themes_url_path');
  424.             $assetUrlPath = $this->container->getParameter('topxia.web_assets_url_path');
  425.             $bundleUrlPath = $this->container->getParameter('topxia.web_bundles_url_path');
  426.             $staticDistUrlPath = $this->container->getParameter('front_end.web_static_dist_url_path');
  427.             preg_match_all('/<img[^>]*src=[\'"]?([^>\'"\s]*)[\'"]?[^>]*>/i', $content, $imgs);
  428.             if ($imgs) {
  429.                 foreach ($imgs[1] as $img) {
  430.                     if (0 === strpos($img, $publicUrlPath)
  431.                         || 0 === strpos($img, $themeUrlPath)
  432.                         || 0 === strpos($img, $assetUrlPath)
  433.                         || 0 === strpos($img, $bundleUrlPath)
  434.                         || 0 === strpos($img, $staticDistUrlPath)) {
  435.                         $content = str_replace('"'.$img, '"'.$cdnUrl.$img, $content);
  436.                     }
  437.                 }
  438.             }
  439.         }
  440.         return $content;
  441.     }
  442.     public function weixinConfig()
  443.     {
  444.         $weixinmob_enabled = $this->getSetting('login_bind.weixinmob_enabled');
  445.         if (!(bool) $weixinmob_enabled) {
  446.             return null;
  447.         }
  448.         $jsApiTicket = $this->createService('User:TokenService')->getTokenByType('jsapi.ticket');
  449.         $key = $this->getSetting('login_bind.weixinmob_key');
  450.         $secret = $this->getSetting('login_bind.weixinmob_secret');
  451.         if (empty($jsApiTicket)) {
  452.             $config = ['key' => $key, 'secret' => $secret];
  453.             $weixinshare = new WeixinShare($config);
  454.             $token = $weixinshare->getJsApiTicket();
  455.             if (empty($token)) {
  456.                 return [];
  457.             }
  458.             $jsApiTicket = $this->createService('User:TokenService')->makeToken(
  459.                 'jsapi.ticket',
  460.                 ['data' => $token, 'duration' => $token['expires_in']]
  461.             );
  462.         }
  463.         $config = [
  464.             'appId' => $key,
  465.             'timestamp' => time(),
  466.             'nonceStr' => uniqid($prefix = 'edusoho'),
  467.             'jsApiList' => ['onMenuShareTimeline', 'onMenuShareAppMessage', 'onMenuShareQZone', 'onMenuShareQQ'],
  468.         ];
  469.         $jsapi_ticket = $jsApiTicket['data']['ticket'];
  470.         $url = $this->container->get('request_stack')->getMainRequest()->getUri();
  471.         $string = 'jsapi_ticket='.$jsapi_ticket.'&noncestr='.$config['nonceStr'].'&timestamp='.$config['timestamp'].'&url='.$url;
  472.         $config['string'] = $string;
  473.         $config['signature'] = sha1($string);
  474.         return json_encode($config);
  475.     }
  476.     public function renderNotification($notification)
  477.     {
  478.         if ($notification) {
  479.             $manager = ExtensionManager::instance();
  480.             $notification['message'] = $manager->renderNotification($notification);
  481.         }
  482.         return $notification;
  483.     }
  484.     public function routeExists($name)
  485.     {
  486.         $router = $this->container->get('router');
  487.         return (null === $router->getRouteCollection()->get($name)) ? false : true;
  488.     }
  489.     public function isWithoutNetwork()
  490.     {
  491.         $network = $this->getSetting('developer.without_network', $default = false);
  492.         return (bool) $network;
  493.     }
  494.     public function getUserVipLevel($userId)
  495.     {
  496.         return $this->createService('VipPlugin:Vip:VipService')->getMemberByUserId($userId);
  497.     }
  498.     public function getParametersFromUrl($url)
  499.     {
  500.         $BaseUrl = parse_url($url);
  501.         if (isset($BaseUrl['query'])) {
  502.             if (strstr($BaseUrl['query'], '&')) {
  503.                 $parameter = explode('&', $BaseUrl['query']);
  504.                 $parameters = [];
  505.                 foreach ($parameter as $key => $value) {
  506.                     $parameters[$key] = explode('=', $value);
  507.                 }
  508.             } else {
  509.                 $parameter = explode('=', $BaseUrl['query']);
  510.                 $parameters = [];
  511.                 $parameters[0] = $parameter;
  512.             }
  513.         } else {
  514.             return null;
  515.         }
  516.         return $parameters;
  517.     }
  518.     public function spaceToNbsp($content)
  519.     {
  520.         $content = str_replace(' ', '&nbsp;', $content);
  521.         return $content;
  522.     }
  523.     public function isMicroMessenger()
  524.     {
  525.         return false !== strpos($this->container->get('request_stack')->getMainRequest()->headers->get('User-Agent'), 'MicroMessenger');
  526.     }
  527.     public function renameLocale($locale)
  528.     {
  529.         $locale = strtolower($locale);
  530.         $locale = str_replace('_', '-', $locale);
  531.         return 'zh-cn' == $locale ? '' : '-'.$locale;
  532.     }
  533.     public function getFingerprint()
  534.     {
  535.         $user = $this->getUserService()->getCurrentUser();
  536.         if (!$user->isLogin()) {
  537.             return '';
  538.         }
  539.         $user = $this->getUserService()->getUser($user['id']);
  540.         // @todo 如果配置用户的关键信息,这个方法存在信息泄漏风险,更换新播放器后解决这个问题。
  541.         $pattern = $this->getSetting('magic.video_fingerprint');
  542.         if ($pattern) {
  543.             $fingerprint = $this->parsePattern($pattern, $user);
  544.         } else {
  545.             $request = $this->container->get('request_stack')->getMainRequest();
  546.             $host = $request->getHttpHost();
  547.             $fingerprint = "{$host} {$user['nickname']}";
  548.         }
  549.         return $fingerprint;
  550.     }
  551.     public function popRewardPointNotify()
  552.     {
  553.         $session = $this->container->get('session');
  554.         if (empty($session)) {
  555.             return '';
  556.         }
  557.         $message = $session->get('Reward-Point-Notify');
  558.         $session->remove('Reward-Point-Notify');
  559.         return $message;
  560.     }
  561.     protected function parsePattern($pattern, $user)
  562.     {
  563.         $profile = $this->getUserService()->getUserProfile($user['id']);
  564.         $values = array_merge($user, $profile);
  565.         $values = array_filter(
  566.             $values,
  567.             function ($value) {
  568.                 return !is_array($value);
  569.             }
  570.         );
  571.         return $this->simpleTemplateFilter($pattern, $values);
  572.     }
  573.     public function subStr($text, $start, $length)
  574.     {
  575.         $text = trim($text);
  576.         $length = (int) $length;
  577.         if (($length > 0) && (mb_strlen($text) > $length)) {
  578.             $text = mb_substr($text, $start, $length, 'UTF-8');
  579.         }
  580.         return $text;
  581.     }
  582.     public function convertEncodingSubStr($text, $start, $length)
  583.     {
  584.         $strlen = (strlen($text) + mb_strlen($text, 'UTF-8')) / 2;
  585.         if ($strlen <= $length) {
  586.             return $text;
  587.         }
  588.         $text = mb_strcut(mb_convert_encoding($text, 'GBK', 'UTF-8'), 0, $length, 'GBK');
  589.         return mb_convert_encoding($text, 'UTF-8', 'GBK').'...';
  590.     }
  591.     public function userCoinAmount($type, $userId, $startDateTime = null, $endDateTime = null)
  592.     {
  593.         // 支付模块已移除,返回默认值
  594.         return 0;
  595.     }
  596.     public function getBalance($userId)
  597.     {
  598.         // 支付模块已移除,返回默认值
  599.         return ['amount' => 0, 'user_id' => $userId];
  600.     }
  601.     /**
  602.      * @return UserService
  603.      */
  604.     private function getUserService()
  605.     {
  606.         return $this->createService('User:UserService');
  607.     }
  608.     public function isExistInSubArrayById($currentTarget, $targetArray)
  609.     {
  610.         foreach ($targetArray as $target) {
  611.             if ($currentTarget['id'] == $target['id']) {
  612.                 return true;
  613.             }
  614.         }
  615.         return false;
  616.     }
  617.     public function getThemeGlobalScript()
  618.     {
  619.         $theme = $this->getSetting('theme.uri', 'default');
  620.         $filePath = realpath(
  621.             $this->container->getParameter('kernel.root_dir')."/../web/themes/{$theme}/js/global-script.js"
  622.         );
  623.         if ($filePath) {
  624.             return 'theme/global-script';
  625.         }
  626.         return '';
  627.     }
  628.     public function isPluginInstalled($name)
  629.     {
  630.         return $this->container->get('kernel')->getPluginConfigurationManager()->isPluginInstalled($name);
  631.     }
  632.     public function canShowClassRoom()
  633.     {
  634.         return CloudSchoolLevelHelper::canShowClassRoom();
  635.     }
  636.     public function canShowArticle()
  637.     {
  638.         return CloudSchoolLevelHelper::canShowArticle();
  639.     }
  640.     public function canShowOfflineActivity()
  641.     {
  642.         return CloudSchoolLevelHelper::canShowOfflineActivity();
  643.     }
  644.     public function canShowMobileApp()
  645.     {
  646.         return CloudSchoolLevelHelper::canShowMobileApp();
  647.     }
  648.     public function canShowArticleGroup()
  649.     {
  650.         return CloudSchoolLevelHelper::canShowArticleGroup();
  651.     }
  652.     public function getPluginVersion($name)
  653.     {
  654.         $plugins = $this->container->get('kernel')->getPlugins();
  655.         foreach ($plugins as $plugin) {
  656.             if (strtolower($plugin['code']) == strtolower($name)) {
  657.                 return $plugin['version'];
  658.             }
  659.         }
  660.         return null;
  661.     }
  662.     public function versionCompare($version1, $version2, $operator)
  663.     {
  664.         return version_compare($version1, $version2, $operator);
  665.     }
  666.     public function getJsPaths($excludedCdnResources = [])
  667.     {
  668.         $cdnUrl = new CdnUrl();
  669.         $basePath = $cdnUrl->get();
  670.         if (empty($basePath)) {
  671.             $basePath = $this->container->get('request_stack')->getMainRequest()->getBasePath();
  672.         }
  673.         $theme = $this->getSetting('theme.uri', 'default');
  674.         $plugins = $this->container->get('kernel')->getPlugins();
  675.         $names = [];
  676.         $newPluginNames = [];
  677.         foreach ($plugins as $plugin) {
  678.             if (is_array($plugin)) {
  679.                 if ('plugin' != $plugin['type']) {
  680.                     continue;
  681.                 }
  682.                 if (isset($plugin['protocol']) && 3 == $plugin['protocol']) {
  683.                     $newPluginNames[] = $plugin['code'].'plugin';
  684.                 } else {
  685.                     $names[] = $plugin['code'];
  686.                 }
  687.             } else {
  688.                 $names[] = $plugin;
  689.             }
  690.         }
  691.         $names[] = 'customweb';
  692.         $names[] = 'customadmin';
  693.         $names[] = 'custom';
  694.         $names[] = 'topxiaweb';
  695.         $names[] = 'topxiaadmin';
  696.         $names[] = 'classroom';
  697.         $names[] = 'materiallib';
  698.         $names[] = 'sensitiveword';
  699.         $names[] = 'permission';
  700.         $names[] = 'org';
  701.         $names[] = 'corporatetraining';
  702.         $paths = [
  703.             'common' => 'common',
  704.             'theme' => "{$basePath}/themes/{$theme}/js",
  705.         ];
  706.         foreach ($names as $name) {
  707.             $name = strtolower($name);
  708.             if (!empty($excludedCdnResources) && in_array($name, $excludedCdnResources)) {
  709.                 $paths["{$name}bundle"] = "/bundles/{$name}/js";
  710.             } else {
  711.                 $paths["{$name}bundle"] = "{$basePath}/bundles/{$name}/js";
  712.             }
  713.         }
  714.         foreach ($newPluginNames as $newPluginName) {
  715.             $newPluginName = strtolower($newPluginName);
  716.             if (!empty($excludedCdnResources) && in_array($newPluginName, $excludedCdnResources)) {
  717.                 $paths["{$newPluginName}"] = "/bundles/{$newPluginName}/js";
  718.             } else {
  719.                 $paths["{$newPluginName}"] = "{$basePath}/bundles/{$newPluginName}/js";
  720.             }
  721.         }
  722.         // $paths['balloon-video-player'] = 'http://player-cdn.edusoho.net/balloon-video-player';
  723.         return $paths;
  724.     }
  725.     public function getContextValue($context, $key)
  726.     {
  727.         $keys = explode('.', $key);
  728.         $value = $context;
  729.         foreach ($keys as $key) {
  730.             if (!isset($value[$key])) {
  731.                 throw new \InvalidArgumentException(sprintf('Key `%s` is not in context with %s', $key, implode(array_keys($context), ', ')));
  732.             }
  733.             $value = $value[$key];
  734.         }
  735.         return $value;
  736.     }
  737.     public function isFeatureEnabled($feature)
  738.     {
  739.         $features = $this->container->hasParameter('enabled_features') ? $this->container->getParameter(
  740.             'enabled_features'
  741.         ) : [];
  742.         return in_array($feature, $features);
  743.     }
  744.     public function getParameter($name, $default = null)
  745.     {
  746.         if (!$this->container->hasParameter($name)) {
  747.             return $default;
  748.         }
  749.         return $this->container->getParameter($name);
  750.     }
  751.     public function makeUpoadToken($group, $type = 'image', $duration = 18000)
  752.     {
  753.         $maker = new UploadToken();
  754.         return $maker->make($group, $type, $duration);
  755.     }
  756.     public function getConvertIP($ip)
  757.     {
  758.         if (!empty($ip)) {
  759.             $location = ConvertIpToolkit::convertIp($ip);
  760.             if ('INNA' === $location) {
  761.                 return '未知区域';
  762.             }
  763.             return $location;
  764.         }
  765.         return '';
  766.     }
  767.     public function getNewConverIP($ip)
  768.     {
  769.         if (!empty($ip)) {
  770.             $location = ConvertIpToolkit::newConvertIp($ip);
  771.             if ('INNA' === $location) {
  772.                 return '未知区域';
  773.             }
  774.             $location = array_filter($location);
  775.             return implode(' ', $location);
  776.         }
  777.         return '';
  778.     }
  779.     public function dateformatFilter($time, $format = '')
  780.     {
  781.         if (empty($time)) {
  782.             return;
  783.         }
  784.         if (empty($format)) {
  785.             return date('Y-m-d H:i', $time);
  786.         }
  787.         return date($format, $time);
  788.     }
  789.     public function smarttimeFilter($time)
  790.     {
  791.         $diff = time() - $time;
  792.         if ($diff < 0) {
  793.             return $this->trans('site.twig.extension.smarttime.future');
  794.         }
  795.         if (0 == $diff) {
  796.             return $this->trans('site.twig.extension.smarttime.hardly');
  797.         }
  798.         if ($diff < 60) {
  799.             return $this->trans('site.twig.extension.smarttime.previous_second', ['%diff%' => $diff]);
  800.         }
  801.         if ($diff < 3600) {
  802.             return $this->trans('site.twig.extension.smarttime.previous_minute', ['%diff%' => round($diff / 60)]);
  803.         }
  804.         if ($diff < 86400) {
  805.             return $this->trans('site.twig.extension.smarttime.previous_hour', ['%diff%' => round($diff / 3600)]);
  806.         }
  807.         if ($diff < 2592000) {
  808.             return $this->trans('site.twig.extension.smarttime.previous_day', ['%diff%' => round($diff / 86400)]);
  809.         }
  810.         if ($diff < 31536000) {
  811.             return date('m-d', $time);
  812.         }
  813.         return date('Y-m-d', $time);
  814.     }
  815.     public function remainTimeFilter($value, $timeType = '')
  816.     {
  817.         $remainTime = [];
  818.         $remain = $value - time();
  819.         if ($remain <= 0 && empty($timeType)) {
  820.             return $remainTime['second'] = '0'.$this->trans('site.date.minute');
  821.         }
  822.         if ($remain <= 3600 && empty($timeType)) {
  823.             return $remainTime['minutes'] = round($remain / 60).$this->trans('site.date.minute');
  824.         }
  825.         if ($remain < 86400 && empty($timeType)) {
  826.             return $remainTime['hours'] = round($remain / 3600).$this->trans('site.date.hour');
  827.         }
  828.         $remainTime['day'] = round(($remain < 0 ? 0 : $remain) / 86400).$this->trans('site.date.day');
  829.         if (!empty($timeType)) {
  830.             return $remainTime[$timeType];
  831.         } else {
  832.             return $remainTime['day'];
  833.         }
  834.     }
  835.     public function getCountdownTime($value)
  836.     {
  837.         $countdown = ['days' => 0, 'hours' => 0, 'minutes' => 0, 'seconds' => 0];
  838.         $remain = $value - time();
  839.         if ($remain <= 0) {
  840.             return $countdown;
  841.         }
  842.         $countdown['days'] = intval($remain / 86400);
  843.         $remain = $remain - 86400 * $countdown['days'];
  844.         $countdown['hours'] = intval($remain / 3600);
  845.         $remain = $remain - 3600 * $countdown['hours'];
  846.         $countdown['minutes'] = intval($remain / 60);
  847.         $remain = $remain - 60 * $countdown['minutes'];
  848.         $countdown['seconds'] = $remain;
  849.         return $countdown;
  850.     }
  851.     public function durationFilter($value)
  852.     {
  853.         $minutes = intval($value / 60);
  854.         $seconds = $value - $minutes * 60;
  855.         return sprintf('%02d', $minutes).':'.sprintf('%02d', $seconds);
  856.     }
  857.     public function durationTextFilter($value)
  858.     {
  859.         $minutes = intval($value / 60);
  860.         $seconds = $value - $minutes * 60;
  861.         if (0 === $minutes) {
  862.             return $seconds.$this->trans('site.date.second');
  863.         }
  864.         return $this->trans('site.twig.extension.time_interval.minute_second', ['%diff_minute%' => $minutes, '%diff_second%' => $seconds]);
  865.     }
  866.     /**
  867.      * 毫秒转 HH:MM:SS 格式,用于 segment 视频展示名等
  868.      */
  869.     public function formatMsToHmsFilter($ms)
  870.     {
  871.         $ms = (int) $ms;
  872.         $s = (int) floor($ms / 1000);
  873.         $h = (int) floor($s / 3600);
  874.         $m = (int) floor(($s % 3600) / 60);
  875.         $s = $s % 60;
  876.         return sprintf('%02d:%02d:%02d', $h, $m, $s);
  877.     }
  878.     public function timeRangeFilter($start, $end)
  879.     {
  880.         $range = date('Y-n-d H:i', $start).' - ';
  881.         if ($this->container->get('topxia.timemachine')->inSameDay($start, $end)) {
  882.             $range .= date('H:i', $end);
  883.         } else {
  884.             $range .= date('Y年n月d日 H:i', $end);
  885.         }
  886.         return $range;
  887.     }
  888.     public function timeDiffFilter($endTime, $diffDay = 0, $startTime = '')
  889.     {
  890.         $endSecond = strtotime(date('Y-m-d', $endTime));
  891.         $startSecond = empty($startTime) ? strtotime(date('Y-m-d', time())) : $startTime;
  892.         $diffDay = round(($endSecond - $startSecond) / 86400, 0, PHP_ROUND_HALF_DOWN); // 丢弃小数点
  893.         return $diffDay > 0 ? $diffDay : 0;
  894.     }
  895.     public function tagsJoinFilter($tagIds)
  896.     {
  897.         if (empty($tagIds) || !is_array($tagIds)) {
  898.             return '';
  899.         }
  900.         $tags = $this->createService('Taxonomy:TagService')->findTagsByIds($tagIds);
  901.         $names = ArrayToolkit::column($tags, 'name');
  902.         return join($names, ',');
  903.     }
  904.     public function navigationUrlFilter($url)
  905.     {
  906.         $url = (string) $url;
  907.         if (strpos($url, '://')) {
  908.             return $url;
  909.         }
  910.         if (!empty($url[0]) && ('/' == $url[0])) {
  911.             return $url;
  912.         }
  913.         return $this->container->get('request_stack')->getMainRequest()->getBaseUrl().'/'.$url;
  914.     }
  915.     /**
  916.      *                            P -> 省全称,     p -> 省简称
  917.      *                            C -> 城市全称,    c -> 城市简称
  918.      *                            D -> 区全称,     d -> 区简称.
  919.      *
  920.      * @param [type] $districeId [description]
  921.      * @param string $format 格式,默认格式'P C D'
  922.      *
  923.      * @return [type] [description]
  924.      */
  925.     public function locationTextFilter($districeId, $format = 'P C D')
  926.     {
  927.         $text = '';
  928.         $names = $this->createService('Taxonomy:LocationService')->getLocationFullName($districeId);
  929.         $len = strlen($format);
  930.         for ($i = 0; $i < $len; ++$i) {
  931.             switch ($format[$i]) {
  932.                 case 'P':
  933.                     $text .= $names['province'];
  934.                     break;
  935.                 case 'p':
  936.                     $text .= $this->mb_trim($names['province'], '省');
  937.                     break;
  938.                 case 'C':
  939.                     $text .= $names['city'];
  940.                     break;
  941.                 case 'c':
  942.                     $text .= $this->mb_trim($names['city'], '市');
  943.                     break;
  944.                 case 'D':
  945.                 case 'd':
  946.                     $text .= $names['district'];
  947.                     break;
  948.                 default:
  949.                     $text .= $format[$i];
  950.                     break;
  951.             }
  952.         }
  953.         return $text;
  954.     }
  955.     public function tagsHtmlFilter($tags, $class = '')
  956.     {
  957.         $links = [];
  958.         $tags = $this->createService('Taxonomy:TagService')->findTagsByIds($tags);
  959.         foreach ($tags as $tag) {
  960.             $url = $this->container->get('router')->generate('course_explore', ['tagId' => $tag['id']]);
  961.             $links[] = "<a href=\"{$url}\" class=\"{$class}\">{$tag['name']}</a>";
  962.         }
  963.         return implode(' ', $links);
  964.     }
  965.     public function parseFileUri($uri)
  966.     {
  967.         $kernel = ServiceKernel::instance();
  968.         return $kernel->createService('Content:FileService')->parseFileUri($uri);
  969.     }
  970.     public function getFilePath($uri, $default = '', $absolute = false)
  971.     {
  972.         $assets = $this->container->get('assets.default_package_util');
  973.         $request = $this->container->get('request_stack')->getMainRequest();
  974.         if (empty($uri)) {
  975.             $url = $assets->getUrl('assets/img/default/'.$default);
  976.             // $url = $request->getBaseUrl() . '/assets/img/default/' . $default;
  977.             if ($absolute) {
  978.                 $url = $request->getSchemeAndHttpHost().$url;
  979.             }
  980.             return $url;
  981.         }
  982.         if (false !== strpos($uri, 'http://')) {
  983.             return $uri;
  984.         }
  985.         $uri = $this->parseFileUri($uri);
  986.         if ('public' == $uri['access']) {
  987.             $url = rtrim($this->container->getParameter('topxia.upload.public_url_path'), ' /').'/'.$uri['path'];
  988.             $url = ltrim($url, ' /');
  989.             $url = $assets->getUrl($url);
  990.             if ($absolute) {
  991.                 $url = $request->getSchemeAndHttpHost().$url;
  992.             }
  993.             return $url;
  994.         }
  995.     }
  996.     public function getDefaultPath($category, $uri = '', $size = '', $absolute = false)
  997.     {
  998.         $assets = $this->container->get('assets.default_package_util');
  999.         $request = $this->container->get('request_stack')->getMainRequest();
  1000.         if (empty($uri)) {
  1001.             $publicUrlpath = 'assets/img/default/';
  1002.             $url = $assets->getUrl($publicUrlpath.$size.$category);
  1003.             $defaultSetting = $this->createService('System:SettingService')->get('default', []);
  1004.             $key = 'default'.ucfirst($category);
  1005.             $fileName = $key.'FileName';
  1006.             if (array_key_exists($key, $defaultSetting) && array_key_exists($fileName, $defaultSetting)) {
  1007.                 if (1 == $defaultSetting[$key]) {
  1008.                     $url = $assets->getUrl($publicUrlpath.$size.$defaultSetting[$fileName]);
  1009.                 }
  1010.             } elseif (array_key_exists($key, $defaultSetting) && $defaultSetting[$key]) {
  1011.                 $uri = $defaultSetting[$size.'Default'.ucfirst($category).'Uri'];
  1012.             } else {
  1013.                 return $url;
  1014.             }
  1015.             if ($absolute) {
  1016.                 $url = $request->getSchemeAndHttpHost().$url;
  1017.             }
  1018.             return $url;
  1019.         }
  1020.         return $this->parseUri($uri, $absolute);
  1021.     }
  1022.     public function avatarPath($user, $type = 'middle', $package = 'user')
  1023.     {
  1024.         $avatar = !empty($user[$type.'Avatar']) ? $user[$type.'Avatar'] : null;
  1025.         if (empty($avatar)) {
  1026.             $avatar = $this->getSetting('avatar.png');
  1027.         }
  1028.         return $this->getFpath($avatar, 'avatar.png', $package);
  1029.     }
  1030.     private function parseUri($uri, $absolute = false, $package = 'content')
  1031.     {
  1032.         if (false !== strpos($uri, 'http://') || false !== strpos($uri, 'https://')) {
  1033.             return $uri;
  1034.         }
  1035.         $assets = $this->container->get('assets.default_package_util');
  1036.         $request = $this->container->get('request_stack')->getMainRequest();
  1037.         if (strpos($uri, '://')) {
  1038.             $uri = $this->parseFileUri($uri);
  1039.             $url = '';
  1040.             if ('public' == $uri['access']) {
  1041.                 $url = $uri['path'];
  1042.             }
  1043.             if ('themes' == $uri['access']) {
  1044.                 return $this->addHost('/'.$uri['access'].'/'.$uri['path'], $absolute, $package);
  1045.             }
  1046.         } else {
  1047.             $url = $uri;
  1048.         }
  1049.         $url = rtrim($this->container->getParameter('topxia.upload.public_url_path'), ' /').'/'.$url;
  1050.         return $this->addHost($url, $absolute, $package);
  1051.     }
  1052.     public function getSystemDefaultPath($defaultKey, $absolute = false)
  1053.     {
  1054.         $assets = $this->container->get('assets.default_package_util');
  1055.         $defaultSetting = $this->getSetting('default', []);
  1056.         if (array_key_exists($defaultKey, $defaultSetting)
  1057.             && $defaultSetting[$defaultKey]
  1058.         ) {
  1059.             $path = $defaultSetting[$defaultKey];
  1060.             return $this->parseUri($path, $absolute);
  1061.         } else {
  1062.             $path = $assets->getUrl('assets/img/default/'.$defaultKey);
  1063.             return $this->addHost($path, $absolute);
  1064.         }
  1065.     }
  1066.     public function makeLazyImg($src, $class = '', $alt = '', $img = 'lazyload_course.png')
  1067.     {
  1068.         $imgpath = $path = $this->container->get('assets.default_package_util')->getUrl('assets/img/default/'.$img);
  1069.         return sprintf('<img src="%s" alt="%s" class="%s" data-echo="%s" />', $imgpath, $alt, $class, $src);
  1070.     }
  1071.     public function loadScript($js)
  1072.     {
  1073.         $js = is_array($js) ? $js : [$js];
  1074.         if ($this->pageScripts) {
  1075.             $this->pageScripts = array_merge($this->pageScripts, $js);
  1076.         } else {
  1077.             $this->pageScripts = $js;
  1078.         }
  1079.     }
  1080.     public function exportScripts()
  1081.     {
  1082.         if (empty($this->pageScripts)) {
  1083.             $this->pageScripts = [];
  1084.         }
  1085.         return array_values(array_unique($this->pageScripts));
  1086.     }
  1087.     public function getFileUrl($uri, $default = '', $absolute = false)
  1088.     {
  1089.         $assets = $this->container->get('assets.default_package_util');
  1090.         $request = $this->container->get('request_stack')->getMainRequest();
  1091.         if (empty($uri)) {
  1092.             $url = $assets->getUrl('assets/img/default/'.$default);
  1093.             if ($absolute) {
  1094.                 $url = $request->getSchemeAndHttpHost().$url;
  1095.             }
  1096.             return $url;
  1097.         }
  1098.         $url = rtrim($this->container->getParameter('topxia.upload.public_url_path'), ' /').'/'.$uri;
  1099.         $url = ltrim($url, ' /');
  1100.         $url = $assets->getUrl($url);
  1101.         if ($absolute) {
  1102.             $url = $request->getSchemeAndHttpHost().$url;
  1103.         }
  1104.         return $url;
  1105.     }
  1106.     public function getFurl($path, $defaultKey = false, $package = 'content')
  1107.     {
  1108.         return $this->getPublicFilePath($path, $defaultKey, true, $package);
  1109.     }
  1110.     public function getFpath($path, $defaultKey = false, $package = 'content')
  1111.     {
  1112.         return $this->getPublicFilePath($path, $defaultKey, false, $package);
  1113.     }
  1114.     private function getPublicFilePath($path, $defaultKey = false, $absolute = false, $package = 'content')
  1115.     {
  1116.         $assets = $this->container->get('assets.default_package_util');
  1117.         if (empty($path)) {
  1118.             $defaultSetting = $this->getSetting('default', []);
  1119.             if ('user_avatar.png' == $defaultKey) {
  1120.                 $defaultKey = 'avatar.png';
  1121.             }
  1122.             if ((('course.png' == $defaultKey && array_key_exists(
  1123.                             'defaultCoursePicture',
  1124.                             $defaultSetting
  1125.                         ) && 1 == $defaultSetting['defaultCoursePicture'])
  1126.                     || ('avatar.png' == $defaultKey && array_key_exists(
  1127.                             'defaultAvatar',
  1128.                             $defaultSetting
  1129.                         ) && 1 == $defaultSetting['defaultAvatar']))
  1130.                 && (array_key_exists($defaultKey, $defaultSetting)
  1131.                     && $defaultSetting[$defaultKey])
  1132.             ) {
  1133.                 $path = $defaultSetting[$defaultKey];
  1134.                 return $this->parseUri($path, $absolute, $package);
  1135.             } else {
  1136.                 return $this->addHost('/assets/img/default/'.$defaultKey, $absolute, $package);
  1137.             }
  1138.         }
  1139.         return $this->parseUri($path, $absolute, $package);
  1140.     }
  1141.     private function addHost($path, $absolute, $package = 'content')
  1142.     {
  1143.         $cdn = new CdnUrl();
  1144.         $cdnUrl = $cdn->get($package);
  1145.         if ($cdnUrl) {
  1146.             $isSecure = $this->container->get('request_stack')->getMainRequest()->isSecure();
  1147.             $protocal = $isSecure ? 'https:' : 'http:';
  1148.             $path = $protocal.$cdnUrl.$path;
  1149.         } elseif ($absolute) {
  1150.             $request = $this->container->get('request_stack')->getMainRequest();
  1151.             $path = $request->getSchemeAndHttpHost().$path;
  1152.         }
  1153.         return $path;
  1154.     }
  1155.     public function basePath($package = 'content')
  1156.     {
  1157.         $cdn = new CdnUrl();
  1158.         $cdnUrl = $cdn->get($package);
  1159.         if ($cdnUrl) {
  1160.             $isSecure = $this->container->get('request_stack')->getMainRequest()->isSecure();
  1161.             $protocal = $isSecure ? 'https:' : 'http:';
  1162.             $path = $protocal.$cdnUrl;
  1163.         } else {
  1164.             $request = $this->container->get('request_stack')->getMainRequest();
  1165.             $path = $request->getSchemeAndHttpHost();
  1166.         }
  1167.         return $path;
  1168.     }
  1169.     public function fileSizeFilter($size)
  1170.     {
  1171.         $currentValue = $currentUnit = null;
  1172.         $unitExps = ['B' => 0, 'KB' => 1, 'MB' => 2, 'GB' => 3];
  1173.         foreach ($unitExps as $unit => $exp) {
  1174.             $divisor = pow(1024, $exp);
  1175.             $currentUnit = $unit;
  1176.             $currentValue = $size / $divisor;
  1177.             if ($currentValue < 1024) {
  1178.                 break;
  1179.             }
  1180.         }
  1181.         return sprintf('%.2f', $currentValue).$currentUnit;
  1182.     }
  1183.     public function numberFilter($number)
  1184.     {
  1185.         if ($number <= 1000) {
  1186.             return $number;
  1187.         }
  1188.         $currentValue = $currentUnit = null;
  1189.         $unitExps = ['千' => 3, '万' => 4, '亿' => 8];
  1190.         foreach ($unitExps as $unit => $exp) {
  1191.             $divisor = pow(10, $exp);
  1192.             $currentUnit = $unit;
  1193.             $currentValue = $number / $divisor;
  1194.             if ($currentValue < 10) {
  1195.                 break;
  1196.             }
  1197.         }
  1198.         return sprintf('%.0f', $currentValue).$currentUnit;
  1199.     }
  1200.     public function loadObject($type, $id)
  1201.     {
  1202.         $kernel = ServiceKernel::instance();
  1203.         switch ($type) {
  1204.             case 'user':
  1205.                 return $kernel->createService('User:UserService')->getUser($id);
  1206.             case 'category':
  1207.                 return $this->getCategoryService()->getCategory($id);
  1208.             case 'course':
  1209.                 return $kernel->createService('Course:CourseService')->getCourse($id);
  1210.             case 'file_group':
  1211.                 return $kernel->createService('Content:FileService')->getFileGroup($id);
  1212.             default:
  1213.                 return null;
  1214.         }
  1215.     }
  1216.     public function plainTextFilter($text, $length = null)
  1217.     {
  1218.         $text = strip_tags($text);
  1219.         $text = str_replace(["\n", "\r", "\t"], '', $text);
  1220.         $text = str_replace('&nbsp;', ' ', $text);
  1221.         $text = trim($text);
  1222.         $length = (int) $length;
  1223.         if (($length > 0) && (mb_strlen($text, 'UTF-8') > $length)) {
  1224.             $text = mb_substr($text, 0, $length, 'UTF-8');
  1225.             $text .= '...';
  1226.         }
  1227.         return $text;
  1228.     }
  1229.     public function subTextFilter($text, $length = null)
  1230.     {
  1231.         $text = strip_tags($text);
  1232.         $text = str_replace(["\n", "\r", "\t"], '', $text);
  1233.         $text = str_replace('&nbsp;', ' ', $text);
  1234.         $text = trim($text);
  1235.         $length = (int) $length;
  1236.         if (($length > 0) && (mb_strlen($text, 'utf-8') > $length)) {
  1237.             $text = mb_substr($text, 0, $length, 'UTF-8');
  1238.             $text .= '...';
  1239.         }
  1240.         return $text;
  1241.     }
  1242.     public function mb_substr($text, $length = null)
  1243.     {
  1244.         $text = strip_tags($text);
  1245.         $text = str_replace(["\n", "\r", "\t"], '', $text);
  1246.         $text = str_replace('&nbsp;', ' ', $text);
  1247.         $text = trim($text);
  1248.         $length = (int) $length;
  1249.         if (($length > 0) && (mb_strlen($text, 'utf-8') > $length)) {
  1250.             $text = mb_substr($text, 0, $length, 'UTF-8');
  1251.         }
  1252.         return $text;
  1253.     }
  1254.     public function getFileType($fileName, $string = null)
  1255.     {
  1256.         $fileName = explode('.', $fileName);
  1257.         if ($string) {
  1258.             $name = strtolower($fileName[count($fileName) - 1]).$string;
  1259.         }
  1260.         return $name;
  1261.     }
  1262.     public function getOrgMsg($orgId)
  1263.     {
  1264.         $orgService = $this->createService('CorporateTrainingBundle:Org:OrgService');
  1265.         $org = $orgService->getOrg($orgId);
  1266.         return $org;
  1267.     }
  1268.     public function getCategoryName($categoryId)
  1269.     {
  1270.         $categoryService = $this->getCategoryService();
  1271.         if (empty($categoryId)) {
  1272.             return '';
  1273.         }
  1274.         $category = $categoryService->getCategory($categoryId);
  1275.         if (empty($category)) {
  1276.             return '';
  1277.         }
  1278.         return $category['name'];
  1279.     }
  1280.     public function getCategoryIdsByCategories(array $categories)
  1281.     {
  1282.         if (empty($categories)) {
  1283.             return [];
  1284.         }
  1285.         return array_column($categories, 'id');
  1286.     }
  1287.     public function chrFilter($index)
  1288.     {
  1289.         return chr($index);
  1290.     }
  1291.     public function isHideThread($id)
  1292.     {
  1293.         $need = $this->createService('Group:ThreadService')->sumGoodsCoinsByThreadId($id);
  1294.         $thread = $this->createService('Group:ThreadService')->getThread($id);
  1295.         $data = explode('[/hide]', $thread['content']);
  1296.         foreach ($data as $key => $value) {
  1297.             $value = ' '.$value;
  1298.             sscanf($value, '%[^[][hide=reply]%[^$$]', $replyContent, $replyHideContent);
  1299.             if ($replyHideContent) {
  1300.                 return true;
  1301.             }
  1302.         }
  1303.         if ($need) {
  1304.             return true;
  1305.         }
  1306.         return false;
  1307.     }
  1308.     public function bbCode2HtmlFilter($bbCode)
  1309.     {
  1310.         $ext = $this;
  1311.         $bbCode = preg_replace_callback(
  1312.             '/\[image\](.*?)\[\/image\]/i',
  1313.             function ($matches) use ($ext) {
  1314.                 $src = $ext->getFileUrl($matches[1]);
  1315.                 return "<img src='{$src}' />";
  1316.             },
  1317.             $bbCode
  1318.         );
  1319.         $bbCode = preg_replace_callback(
  1320.             '/\[audio.*?id="(\d+)"\](.*?)\[\/audio\]/i',
  1321.             function ($matches) {
  1322.                 return "<span class='audio-play-trigger' href='javascript:;' data-file-id=\"{$matches[1]}\" data-file-type=\"audio\"></span>";
  1323.             },
  1324.             $bbCode
  1325.         );
  1326.         return $bbCode;
  1327.     }
  1328.     public function scoreTextFilter($text)
  1329.     {
  1330.         $text = number_format($text, 1, '.', '');
  1331.         if ((int) $text == $text) {
  1332.             return (string) (int) $text;
  1333.         }
  1334.         return $text;
  1335.     }
  1336.     public function simpleTemplateFilter($text, $variables)
  1337.     {
  1338.         foreach ($variables as $key => $value) {
  1339.             $text = str_replace('{{'.$key.'}}', $value, $text);
  1340.         }
  1341.         return $text;
  1342.     }
  1343.     public function fillQuestionStemTextFilter($stem)
  1344.     {
  1345.         return preg_replace('/\[\[.+?\]\]+/', '____', $stem);
  1346.     }
  1347.     public function fillQuestionStemHtmlFilter($stem)
  1348.     {
  1349.         $index = 0;
  1350.         $stem = preg_replace_callback(
  1351.             '/\[\[.+?\]\]+/',
  1352.             function ($matches) use (&$index) {
  1353.                 ++$index;
  1354.                 return "<span class='question-stem-fill-blank'>({$index})</span>";
  1355.             },
  1356.             $stem
  1357.         );
  1358.         return $stem;
  1359.     }
  1360.     public function getCourseidFilter($target)
  1361.     {
  1362.         $target = explode('/', $target);
  1363.         $target = explode('-', $target[0]);
  1364.         return $target[1];
  1365.     }
  1366.     public function getPurifyHtml($html, $trusted = false)
  1367.     {
  1368.         if (empty($html)) {
  1369.             return '';
  1370.         }
  1371.         $biz = $this->container->get('biz');
  1372.         return $biz['html_helper']->purify($html, $trusted);
  1373.     }
  1374.     public function atFilter($text, $ats = [])
  1375.     {
  1376.         if (empty($ats) || !is_array($ats)) {
  1377.             return $text;
  1378.         }
  1379.         $router = $this->container->get('router');
  1380.         foreach ($ats as $nickname => $userId) {
  1381.             $path = $router->generate('user_show', ['id' => $userId]);
  1382.             $html = "<a href=\"{$path}\" data-uid=\"{$userId}\" target=\"_blank\">@{$nickname}</a>";
  1383.             $text = preg_replace("/@{$nickname}/ui", $html, $text);
  1384.         }
  1385.         return $text;
  1386.     }
  1387.     public function removeCopyright($source)
  1388.     {
  1389.         if ($this->getSetting('copyright.owned', false)) {
  1390.             $source = str_ireplace('edusoho', '', $source);
  1391.         }
  1392.         return $source;
  1393.     }
  1394.     public function getSetting($name, $default = null)
  1395.     {
  1396.         $names = explode('.', $name);
  1397.         $name = array_shift($names);
  1398.         if (empty($name)) {
  1399.             return $default;
  1400.         }
  1401.         $value = $this->createService('System:SettingService')->get($name);
  1402.         if (!isset($value)) {
  1403.             return $default;
  1404.         }
  1405.         if (empty($names)) {
  1406.             return $value;
  1407.         }
  1408.         $result = $value;
  1409.         foreach ($names as $name) {
  1410.             if (!isset($result[$name])) {
  1411.                 return $default;
  1412.             }
  1413.             $result = $result[$name];
  1414.         }
  1415.         return $result;
  1416.     }
  1417.     public function getOrderPayment($order, $default = null)
  1418.     {
  1419.         $coinSettings = $this->createService('System:SettingService')->get('coin', []);
  1420.         if (!isset($coinSettings['price_type'])) {
  1421.             $coinSettings['price_type'] = 'RMB';
  1422.         }
  1423.         if (!isset($coinSettings['coin_enabled'])) {
  1424.             $coinSettings['coin_enabled'] = 0;
  1425.         }
  1426.         if (1 != $coinSettings['coin_enabled'] || 'coin' != $coinSettings['price_type']) {
  1427.             if ($order['coinAmount'] > 0 && 0 == $order['amount']) {
  1428.                 $default = '余额支付';
  1429.             } else {
  1430.                 $dictExtension = $this->container->get('codeages_plugin.dict_twig_extension');
  1431.                 $default = $dictExtension->getDictText('payment', $order['payment']);
  1432.             }
  1433.         }
  1434.         return $default;
  1435.     }
  1436.     public function isPermitRole($classroomId, $permission, $isStudentOrAuditor = false)
  1437.     {
  1438.         $funcName = 'can'.$permission.'Classroom';
  1439.         if ($isStudentOrAuditor) {
  1440.             return $this->createService('Classroom:ClassroomService')->$funcName($classroomId, $isStudentOrAuditor);
  1441.         }
  1442.         return $this->createService('Classroom:ClassroomService')->$funcName($classroomId);
  1443.     }
  1444.     public function calculatePercent($number, $total)
  1445.     {
  1446.         if (0 == $number || 0 == $total) {
  1447.             return '0%';
  1448.         }
  1449.         if ($number >= $total) {
  1450.             return '100%';
  1451.         }
  1452.         return round($number / $total * 100).'%';
  1453.     }
  1454.     public function rateFormat($rate, $precision = 0)
  1455.     {
  1456.         return round($rate, $precision);
  1457.     }
  1458.     public function arrayMerge($text, $content)
  1459.     {
  1460.         return array_merge($text, $content);
  1461.     }
  1462.     public function getSetPrice($price)
  1463.     {
  1464.         return NumberToolkit::roundUp($price);
  1465.     }
  1466.     public function getCategoryChoices($groupCode, $isFilter = false, $indent = ' ')
  1467.     {
  1468.         $builder = new CategoryBuilder();
  1469.         return $builder->buildChoices($groupCode, $isFilter, $indent);
  1470.     }
  1471.     public function getCategoryChoicesWithCategoryEmpty($groupName, $isFilter = false, $indent = ' ')
  1472.     {
  1473.         $builder = new CategoryBuilder();
  1474.         $choices = $builder->buildChoices($groupName, $isFilter, $indent);
  1475.         $newChoices[-1] = '未分类';
  1476.         return $newChoices + $choices;
  1477.     }
  1478.     public function getNextExecutedTime()
  1479.     {
  1480.         return $this->createService('Crontab:CrontabService')->getNextExcutedTime();
  1481.     }
  1482.     public function getUploadMaxFilesize($formated = true)
  1483.     {
  1484.         $max = FileToolkit::getMaxFilesize();
  1485.         if ($formated) {
  1486.             return FileToolkit::formatFileSize($max);
  1487.         }
  1488.         return $max;
  1489.     }
  1490.     public function isTrial()
  1491.     {
  1492.         if (file_exists($this->container->getParameter('kernel.root_dir').'/data/trial.lock')) {
  1493.             return true;
  1494.         }
  1495.         return false;
  1496.     }
  1497.     public function timestamp()
  1498.     {
  1499.         return time();
  1500.     }
  1501.     public function blurUserName($name)
  1502.     {
  1503.         return mb_substr($name, 0, 1, 'UTF-8').'**';
  1504.     }
  1505.     public function blur_phone_number($phoneNum)
  1506.     {
  1507.         $head = substr($phoneNum, 0, 3);
  1508.         $tail = substr($phoneNum, -4, 4);
  1509.         return $head.'****'.$tail;
  1510.     }
  1511.     public function blur_idcard_number($idcardNum)
  1512.     {
  1513.         $head = substr($idcardNum, 0, 4);
  1514.         $tail = substr($idcardNum, -2, 2);
  1515.         return $head.'************'.$tail;
  1516.     }
  1517.     public function blur_number($string)
  1518.     {
  1519.         if (SimpleValidator::email($string)) {
  1520.             $head = substr($string, 0, 1);
  1521.             $tail = substr($string, strpos($string, '@'));
  1522.             return $head.'***'.$tail;
  1523.         } elseif (SimpleValidator::mobile($string)) {
  1524.             $head = substr($string, 0, 3);
  1525.             $tail = substr($string, -4, 4);
  1526.             return $head.'****'.$tail;
  1527.         } elseif (SimpleValidator::bankCardId($string)) {
  1528.             $tail = substr($string, -4, 4);
  1529.             return '**** **** **** '.$tail;
  1530.         } elseif (SimpleValidator::idcard($string)) {
  1531.             $head = substr($string, 0, 4);
  1532.             $tail = substr($string, -2, 2);
  1533.             return $head.'************'.$tail;
  1534.         }
  1535.     }
  1536.     public function mathFormat($number, $multiplicator)
  1537.     {
  1538.         $number *= $multiplicator;
  1539.         return $number;
  1540.     }
  1541.     protected function createService($alias)
  1542.     {
  1543.         return $this->biz->service($alias);
  1544.     }
  1545.     protected function getAppService()
  1546.     {
  1547.         return $this->createService('CloudPlatform:AppService');
  1548.     }
  1549.     public function getPurifyAndTrimHtml($html)
  1550.     {
  1551.         $html = strip_tags($html, '');
  1552.         return preg_replace("/(\s|\&nbsp\;| |\xc2\xa0)/", '', $html);
  1553.     }
  1554.     public function arrayColumn($array, $column)
  1555.     {
  1556.         return ArrayToolkit::column($array, $column);
  1557.     }
  1558.     private function trans($key, $parameters = [])
  1559.     {
  1560.         return $this->container->get('translator')->trans($key, $parameters);
  1561.     }
  1562.     public function mb_trim($string, $charlist = '\\\\s', $ltrim = true, $rtrim = true)
  1563.     {
  1564.         $bothEnds = $ltrim && $rtrim;
  1565.         $charClassInner = preg_replace(
  1566.             ['/[\^\-\]\\\]/S', '/\\\{4}/S'],
  1567.             ['\\\\\\0', '\\'],
  1568.             $charlist
  1569.         );
  1570.         $workHorse = '['.$charClassInner.']+';
  1571.         $ltrim && $leftPattern = '^'.$workHorse;
  1572.         $rtrim && $rightPattern = $workHorse.'$';
  1573.         if ($bothEnds) {
  1574.             $patternMiddle = $leftPattern.'|'.$rightPattern;
  1575.         } elseif ($ltrim) {
  1576.             $patternMiddle = $leftPattern;
  1577.         } else {
  1578.             $patternMiddle = $rightPattern;
  1579.         }
  1580.         return preg_replace("/$patternMiddle/usSD", '', $string);
  1581.     }
  1582.     public function wrap($object, $type)
  1583.     {
  1584.         return $this->container->get('web.wrapper')->handle($object, $type);
  1585.     }
  1586.     public function convertAbsoluteUrl($html)
  1587.     {
  1588.         $html = preg_replace_callback('/src=[\'\"]\/(.*?)[\'\"]/', function ($matches) {
  1589.             $cdn = new CdnUrl();
  1590.             $cdnUrl = $cdn->get('content');
  1591.             if (!empty($cdnUrl)) {
  1592.                 $absoluteUrl = AssetHelper::getScheme().':'.rtrim($cdnUrl, '/').'/'.ltrim($matches[1], '/');
  1593.             } else {
  1594.                 $absoluteUrl = AssetHelper::uriForPath('/'.ltrim($matches[1], '/'));
  1595.             }
  1596.             return "src=\"{$absoluteUrl}\"";
  1597.         }, $html);
  1598.         return $html;
  1599.     }
  1600.     public function getLoginEmailAddress($email)
  1601.     {
  1602.         $dress = explode('@', $email);
  1603.         $dress = strtolower($dress[1]);
  1604.         $emailAddressMap = [
  1605.             'gmail.com' => 'mail.google.com',
  1606.             'vip.qq.com' => 'mail.qq.com',
  1607.             'vip.163.com' => 'vip.163.com',
  1608.             'vip.sina.com' => 'mail.sina.com.cn',
  1609.             'foxmail.com' => 'mail.qq.com',
  1610.             'hotmail.com' => 'www.hotmail.com',
  1611.             '188.com' => 'www.188.com',
  1612.             '139.com' => 'mail.10086.cn',
  1613.             '126.com' => 'www.126.com',
  1614.             'yeah.net' => 'yeah.net',
  1615.         ];
  1616.         if (!empty($emailAddressMap[$dress])) {
  1617.             return 'http://'.$emailAddressMap[$dress];
  1618.         }
  1619.         return 'http://mail.'.$dress;
  1620.     }
  1621.     public function getCloudSdkUrl($type)
  1622.     {
  1623.         return $this->getResourceFacadeService()->getFrontPlaySDKPathByType($type);
  1624.     }
  1625.     public function isWechatLoginBind()
  1626.     {
  1627.         $wechat = $this->isMicroMessenger();
  1628.         $loginBind = $this->getSetting('login_bind');
  1629.         return $wechat && !empty($loginBind['enabled']) && !empty($loginBind['weixinmob_enabled']);
  1630.     }
  1631.     public function makeLocalMediaFileToken($file)
  1632.     {
  1633.         $token = $this->makeToken('local.media', $file['id']);
  1634.         return $token['token'];
  1635.     }
  1636.     /**
  1637.      * 获取在线咨询授权信息
  1638.      *
  1639.      * @return array
  1640.      */
  1641.     public function onlineAdvisoryAuthInfo()
  1642.     {
  1643.         $info = [
  1644.             'isCustom' => 'none',
  1645.         ];
  1646.         try {
  1647.             $api = CloudAPIFactory::create('root');
  1648.             $info = $api->get('/me');
  1649.             $displayLevel = ['personal', 'basic', 'medium', 'advanced', 'gold', 'custom'];
  1650.             if (is_array($info) && isset($info['level']) && in_array($info['level'], $displayLevel)) {
  1651.                 $info['isCustom'] = ('custom' == $info['level']) ? '是' : '否';
  1652.             } else {
  1653.                 $info['isCustom'] = 'none';
  1654.             }
  1655.         } catch (\RuntimeException $e) {
  1656.         }
  1657.         return $info;
  1658.     }
  1659.     public function getSyncModeDict()
  1660.     {
  1661.         $dict = [
  1662.             'closed' => $this->trans('admin.sync-account-docking.btn.closed_btn'),
  1663.             'dingtalk' => $this->trans('admin.sync-account-docking.btn.dingtalk_btn'),
  1664.         ];
  1665.         if ($this->isPluginInstalled('WorkWechat')) {
  1666.             $dict['work_wechat'] = $this->trans('admin.sync-account-docking.btn.work_wechat_btn');
  1667.         }
  1668.         if ($this->isPluginInstalled('FeiShu')) {
  1669.             $dict['feishu'] = $this->trans('admin.sync-account-docking.btn.feishu_btn');
  1670.         }
  1671.         if ($this->isPluginInstalled('LDAP')) {
  1672.             $dict['LDAP'] = 'LDAP';
  1673.         }
  1674.         return $dict;
  1675.     }
  1676.     protected function makeToken($type, $fileId, $context = [])
  1677.     {
  1678.         $times = ('local.media' == $type) ? 100 : 10;
  1679.         $duration = ('local.media' == $type) ? 7200 : 3600;
  1680.         $fields = [
  1681.             'data' => [
  1682.                 'id' => $fileId,
  1683.             ],
  1684.             'times' => $times,
  1685.             'duration' => $duration,
  1686.             'userId' => $this->biz['user']['id'],
  1687.         ];
  1688.         if (isset($context['watchTimeLimit'])) {
  1689.             $fields['data']['watchTimeLimit'] = $context['watchTimeLimit'];
  1690.         }
  1691.         if (isset($context['hideBeginning'])) {
  1692.             $fields['data']['hideBeginning'] = $context['hideBeginning'];
  1693.         }
  1694.         return $this->getTokenService()->makeToken($type, $fields);
  1695.     }
  1696.     public function isHiddenVideoHeader($isHidden = false)
  1697.     {
  1698.         $storage = $this->getSetting('storage');
  1699.         if (!empty($storage) && array_key_exists('video_header', $storage) && $storage['video_header'] && !$isHidden) {
  1700.             return false;
  1701.         }
  1702.         return true;
  1703.     }
  1704.     public function canSendMessage($userId)
  1705.     {
  1706.         $user = $this->biz['user'];
  1707.         if (!$user->isLogin()) {
  1708.             return false;
  1709.         }
  1710.         if (in_array('ROLE_ADMIN', $user['roles']) || $user->isSuperAdmin()) {
  1711.             return true;
  1712.         }
  1713.         $toUser = $this->getUserService()->getUser($userId);
  1714.         if ($user['id'] == $toUser['id']) {
  1715.             return false;
  1716.         }
  1717.         if (in_array('ROLE_ADMIN', $toUser['roles']) || in_array('ROLE_SUPER_ADMIN', $toUser['roles'])) {
  1718.             return true;
  1719.         }
  1720.         $messageSetting = $this->getSetting('message', []);
  1721.         if (empty($messageSetting['teacherToStudent']) && $this->isTeacher($user['roles']) && $this->isOnlyStudent($toUser['roles'])) {
  1722.             return false;
  1723.         }
  1724.         if (empty($messageSetting['studentToStudent']) && $this->isOnlyStudent($user['roles']) && $this->isOnlyStudent($toUser['roles'])) {
  1725.             return false;
  1726.         }
  1727.         if (empty($messageSetting['studentToTeacher']) && $this->isOnlyStudent($user['roles']) && $this->isTeacher($toUser['roles'])) {
  1728.             return false;
  1729.         }
  1730.         return true;
  1731.     }
  1732.     public function daysOfWeek($day)
  1733.     {
  1734.         $weekName = ['日', '一', '二', '三', '四', '五', '六'];
  1735.         if (isset($weekName[$day])) {
  1736.             return $weekName[$day];
  1737.         }
  1738.         return false;
  1739.     }
  1740.     public function filterArrayKeys($array, $keys)
  1741.     {
  1742.         foreach ($keys as $key) {
  1743.             unset($array[$key]);
  1744.         }
  1745.         return $array;
  1746.     }
  1747.     public function examScore($score)
  1748.     {
  1749.         return $score > 0 ? $score : 0;
  1750.     }
  1751.     public function formatLearnTime($learnTime)
  1752.     {
  1753.         if ($learnTime < 3600) {
  1754.             return round($learnTime / 60).$this->trans('site.data.minute');
  1755.         }
  1756.         return round($learnTime / 3600, 1).$this->trans('site.date.hour');
  1757.     }
  1758.     public function groupBy($array, $key)
  1759.     {
  1760.         return ArrayToolkit::group($array, $key);
  1761.     }
  1762.     private function isTeacher($roles)
  1763.     {
  1764.         return in_array('ROLE_TEACHER', $roles);
  1765.     }
  1766.     private function isOnlyStudent($roles)
  1767.     {
  1768.         return in_array('ROLE_USER', $roles) && !in_array('ROLE_TEACHER', $roles) && !in_array('ROLE_ADMIN', $roles) && !in_array('ROLE_SUPER_ADMIN', $roles);
  1769.     }
  1770.     public function arraysKeyConvert($arrays, $beforeKey, $afterKey)
  1771.     {
  1772.         foreach ($arrays as $key => $value) {
  1773.             if ($value == $beforeKey) {
  1774.                 $arrays[$key][$afterKey] = $arrays[$key][$beforeKey];
  1775.                 unset($arrays[$key][$beforeKey]);
  1776.             }
  1777.         }
  1778.         return $arrays;
  1779.     }
  1780.     public function questionHtmlFilter($html, $allowed = '')
  1781.     {
  1782.         if (!isset($html)) {
  1783.             return '';
  1784.         }
  1785.         $html = preg_replace('/(<img .*?src=")(.*?)(".*?>)/is', '[图片]', $html);
  1786.         $security = $this->getSettingService()->get('security');
  1787.         if (!empty($security['safe_iframe_domains'])) {
  1788.             $safeDomains = $security['safe_iframe_domains'];
  1789.         } else {
  1790.             $safeDomains = [];
  1791.         }
  1792.         $config = [
  1793.             'cacheDir' => $this->biz['cache_directory'].'/htmlpurifier',
  1794.             'safeIframeDomains' => $safeDomains,
  1795.         ];
  1796.         $this->warmUp($config['cacheDir']);
  1797.         $htmlConfig = \HTMLPurifier_Config::createDefault();
  1798.         $htmlConfig->set('Cache.SerializerPath', $config['cacheDir']);
  1799.         $htmlConfig->set('HTML.Allowed', $allowed);
  1800.         $htmlpurifier = new \HTMLPurifier($htmlConfig);
  1801.         return $htmlpurifier->purify($html);
  1802.     }
  1803.     public function uniqid()
  1804.     {
  1805.         return MathToolkit::uniqid();
  1806.     }
  1807.     public function canOperateQuestion($user, $question)
  1808.     {
  1809.         $currentUser = $this->biz['user'];
  1810.         if (in_array('ROLE_SUPER_ADMIN', $user['roles'])) {
  1811.             return true;
  1812.         }
  1813.         if ($currentUser->hasPermission('admin_question_gather_manage')) {
  1814.             $gather = $this->getGatherService()->getGather($question['gatherId']);
  1815.             if (!empty($gather) && $this->getGatherService()->tryManageGather($gather)) {
  1816.                 return true;
  1817.             }
  1818.         }
  1819.         return in_array('ROLE_TRAINING_ADMIN', $user['roles']) && $question['createdUserId'] === $user['id'];
  1820.     }
  1821.     /**
  1822.      * @return GatherService
  1823.      */
  1824.     protected function getGatherService()
  1825.     {
  1826.         return $this->biz->service('Gather:GatherService');
  1827.     }
  1828.     public function showFeedbackBar($route)
  1829.     {
  1830.         if (in_array($route, [
  1831.             'course_teacher_evaluate_list',
  1832.             'project_plan_create',
  1833.             'project_plan_offline_course_homework_list',
  1834.             'survey_result_statistics',
  1835.         ])) {
  1836.             return true;
  1837.         }
  1838.         $allowedRoutes = [
  1839.             'course_set_manage',
  1840.             'course_manage',
  1841.             'classroom_manage',
  1842.             'offline_course_manage',
  1843.             'project_plan_study_data',
  1844.             'questionnaire_manage',
  1845.             'offline_activity_manage',
  1846.             'project_plan',
  1847.             'survey',
  1848.             'admin',
  1849.         ];
  1850.         foreach ($allowedRoutes as $allowedRoute) {
  1851.             if (0 !== stripos($route, $allowedRoute)) {
  1852.                 continue;
  1853.             }
  1854.             if (!in_array($allowedRoute, ['project_plan', 'survey'])) {
  1855.                 return true;
  1856.             }
  1857.             if (false !== stripos($route, 'manage')) {
  1858.                 return true;
  1859.             }
  1860.         }
  1861.         if (false !== stripos($route, 'verify_list')) {
  1862.             return true;
  1863.         }
  1864.         return false;
  1865.     }
  1866.     public function isSaaS()
  1867.     {
  1868.         if ($this->isWithoutNetwork()) {
  1869.             return false;
  1870.         }
  1871.         $site = $this->getSetting('site');
  1872.         if (empty($site['level']) || ($site['levelExpired'] ?? 0) < time()) {
  1873.             try {
  1874.                 $api = CloudAPIFactory::create('root');
  1875.                 $info = $api->get('/me');
  1876.             } catch (\RuntimeException $e) {
  1877.                 $info = [];
  1878.             }
  1879.             $site['level'] = $info['level'] ?? '';
  1880.             $site['levelExpired'] = time() + 7200;
  1881.             $this->getSettingService()->set('site', $site);
  1882.         }
  1883.         return in_array($this->getSetting('site.level'), $this->getSaaSLevels());
  1884.     }
  1885.     public function getSaaSLevels()
  1886.     {
  1887.         return CloudSchoolLevelConstant::SAAS_LEVELS;
  1888.     }
  1889.     public function getRememberMeDeadlineRangeText()
  1890.     {
  1891.         $rememberMeLifetime = LoginToolkit::getRememberMeLifetime();
  1892.         if ($rememberMeLifetime < 60 * 60) {
  1893.             return (int) ($rememberMeLifetime / 60).$this->trans('site.data.minute');
  1894.         }
  1895.         if ($rememberMeLifetime < 24 * 60 * 60) {
  1896.             return (int) ($rememberMeLifetime / (60 * 60)).$this->trans('site.date.hour');
  1897.         }
  1898.         return (int) ($rememberMeLifetime / (24 * 60 * 60)).$this->trans('site.date.day');
  1899.     }
  1900.     protected function warmUp($cacheDir)
  1901.     {
  1902.         if (!@mkdir($cacheDir, 0777, true) && !is_dir($cacheDir)) {
  1903.             throw new ServiceException('mkdir cache dir error');
  1904.         }
  1905.         if (!is_writable($cacheDir)) {
  1906.             chmod($cacheDir, 0777);
  1907.         }
  1908.     }
  1909.     public function isQuestionAnswered($id)
  1910.     {
  1911.         $question = $this->getQuestionService()->get($id);
  1912.         if (empty($question)) {
  1913.             return 0;
  1914.         }
  1915.         $count = $this->getTestpaperService()->countItemResults(['questionId' => $question['id']]);
  1916.         if ($count) {
  1917.             return 1;
  1918.         }
  1919.         if ($this->isPluginInstalled('Exam')) {
  1920.             $count = $this->getExamPluginTestPaperService()->countTestPaperItemResults(['questionId' => $question['id']]);
  1921.             if ($count) {
  1922.                 return 1;
  1923.             }
  1924.         }
  1925.         return 0;
  1926.     }
  1927.     public function isEnabledLms()
  1928.     {
  1929.         return $this->getLmsService()->isEnabledLms();
  1930.     }
  1931.     public function isLmsVersionSupported($version)
  1932.     {
  1933.         return $this->getLmsService()->isLmsVersionSupported($version);
  1934.     }
  1935.     public function knowledgeHref($knowledgeId, $knowledgeBaseId)
  1936.     {
  1937.         return KnowledgeUrlBuilder::build($knowledgeId, $knowledgeBaseId, DeviceToolkit::isMobileClient());
  1938.     }
  1939.     public function mainRequestRoute()
  1940.     {
  1941.         return $this->container->get('request_stack')->getMainRequest()->attributes->get('_route');
  1942.     }
  1943.     public function hasLiveTasks($courseId)
  1944.     {
  1945.         $tasks = $this->getTaskService()->findTasksByCourseId($courseId);
  1946.         $hasLiveTasks = ArrayToolkit::some($tasks, function ($task) {
  1947.             return 'live' === $task['type'];
  1948.         });
  1949.         return $hasLiveTasks;
  1950.     }
  1951.     /**
  1952.      * @return LmsService
  1953.      */
  1954.     protected function getLmsService()
  1955.     {
  1956.         return $this->createService('Lms:LmsService');
  1957.     }
  1958.     protected function getSettingService()
  1959.     {
  1960.         return $this->biz->service('System:SettingService');
  1961.     }
  1962.     /**
  1963.      * @return ResourceFacadeService
  1964.      */
  1965.     protected function getResourceFacadeService()
  1966.     {
  1967.         return $this->createService('CloudPlatform:ResourceFacadeService');
  1968.     }
  1969.     /**
  1970.      * @return TagService
  1971.      */
  1972.     protected function getTagService()
  1973.     {
  1974.         return $this->createService('Taxonomy:TagService');
  1975.     }
  1976.     /**
  1977.      * @return TokenService
  1978.      */
  1979.     protected function getTokenService()
  1980.     {
  1981.         return $this->createService('User:TokenService');
  1982.     }
  1983.     /**
  1984.      * @return mixed
  1985.      */
  1986.     public function getCategoryService()
  1987.     {
  1988.         return $this->createService('Taxonomy:CategoryService');
  1989.     }
  1990.     /**
  1991.      * @return ReviewCenterService
  1992.      */
  1993.     protected function getReviewCenterService()
  1994.     {
  1995.         return $this->createService('ReviewCenter:ReviewCenterService');
  1996.     }
  1997.     protected function getQuestionService()
  1998.     {
  1999.         return $this->createService('Question:QuestionService');
  2000.     }
  2001.     protected function getTestpaperService()
  2002.     {
  2003.         return $this->createService('Testpaper:TestpaperService');
  2004.     }
  2005.     protected function getExamPluginTestPaperService()
  2006.     {
  2007.         return $this->createService('ExamPlugin:TestPaper:TestPaperService');
  2008.     }
  2009.     /**
  2010.      * @return TaskService
  2011.      */
  2012.     protected function getTaskService()
  2013.     {
  2014.         return $this->createService('Task:TaskService');
  2015.     }
  2016.     /**
  2017.      * 判断当前租户是否为 AI 教练租户
  2018.      * 当 is_ai_coach_tenant.enabled = 1 时,隐藏 AI 教练不需要的相关功能入口
  2019.      *
  2020.      * @return bool
  2021.      */
  2022.     public function isAiCoachTenant()
  2023.     {
  2024.         try {
  2025.             return \Biz\System\AiCoachTenantSetting::isEnabled(
  2026.                 $this->getSettingService()->get('is_ai_coach_tenant', [])
  2027.             );
  2028.         } catch (\Exception $e) {
  2029.             return false;
  2030.         }
  2031.     }
  2032. }