# Copyright (C) 2012-2024 Zammad Foundation, https://zammad-foundation.org/ class TicketArticlesController < ApplicationController include CreatesTicketArticles include ClonesTicketArticleAttachments prepend_before_action -> { authorize! }, only: %i[index import_example import_start] prepend_before_action :authentication_check # GET /articles def index model_index_render(Ticket::Article, params) end # GET /articles/1 def show article = Ticket::Article.find(params[:id]) authorize!(article) if response_expand? result = article.attributes_with_association_names render json: result, status: :ok return end if response_full? full = Ticket::Article.full(params[:id]) render json: full return end render json: article.attributes_with_association_names end # GET /ticket_articles/by_ticket/1 def index_by_ticket ticket = Ticket.find(params[:id]) authorize!(ticket, :show?) articles = [] if response_expand? ticket.articles.each do |article| next if !authorized?(article, :show?) result = article.attributes_with_association_names articles.push result end render json: articles, status: :ok return end if response_full? assets = {} record_ids = [] ticket.articles.each do |article| next if !authorized?(article, :show?) record_ids.push article.id assets = article.assets({}) end render json: { record_ids: record_ids, assets: assets, }, status: :ok return end ticket.articles.each do |article| next if !authorized?(article, :show?) articles.push article.attributes_with_association_names end render json: articles, status: :ok end # POST /articles def create ticket = Ticket.find(params[:ticket_id]) authorize!(ticket) article = article_create(ticket, params) if response_expand? result = article.attributes_with_association_names render json: result, status: :created return end if response_full? full = Ticket::Article.full(params[:id]) render json: full, status: :created return end render json: article.attributes_with_association_names, status: :created end # PUT /articles/1 def update article = Ticket::Article.find(params[:id]) authorize!(article) # only update internal and highlight info clean_params = {} if !params[:internal].nil? clean_params[:internal] = params[:internal] end if params.dig(:preferences, :highlight).present? clean_params = article.param_preferences_merge(clean_params.merge( preferences: { highlight: params[:preferences][:highlight].to_s } )) end article.update!(clean_params) if response_expand? result = article.attributes_with_association_names render json: result, status: :ok return end if response_full? full = Ticket::Article.full(params[:id]) render json: full, status: :ok return end render json: article.attributes_with_association_names, status: :ok end # DELETE /api/v1/ticket_articles/:id def destroy article = Ticket::Article.find(params[:id]) authorize!(article) article.destroy! render json: {}, status: :ok end # POST /ticket_attachment_upload_clone_by_article def ticket_attachment_upload_clone_by_article article = Ticket::Article.find(params[:article_id]) authorize!(article.ticket, :show?) render json: { attachments: article_attachments_clone(article), } end # GET /ticket_attachment/:ticket_id/:article_id/:id def attachment ticket = Ticket.lookup(id: params[:ticket_id]) authorize!(ticket, :show?) article = Ticket::Article.find(params[:article_id]) if ticket.id != article.ticket_id # check if requested ticket got merged if ticket.state.state_type.name != 'merged' raise Exceptions::Forbidden, __('The article does not belong to the specified ticket.') end ticket = article.ticket authorize!(ticket, :show?) end list = article.attachments || [] access = false list.each do |item| if item.id.to_i == params[:id].to_i access = true end end raise Exceptions::Forbidden, __('The file does not belong to the specified article.') if !access # preview calendar attachments return render_calendar_preview if params[:view] == 'preview' && params[:type] == 'calendar' content = download_file.content(params[:view]) send_data( content, filename: download_file.filename, type: download_file.content_type, disposition: download_file.disposition ) end # GET /ticket_article_plain/1 def article_plain article = Ticket::Article.find(params[:id]) authorize!(article, :show?) file = article.as_raw # find file return if !file send_data( file.content, filename: file.filename, type: 'message/rfc822', disposition: 'inline' ) end # @path [GET] /ticket_articles/import_example # # @summary Download of example CSV file. # @notes The requester have 'admin' permissions to be able to download it. # @example curl -u #{login}:#{password} http://localhost:3000/api/v1/ticket_articles/import_example # # @response_message 200 File download. # @response_message 403 Forbidden / Invalid session. def import_example csv_string = Ticket::Article.csv_example( col_sep: ',', ) send_data( csv_string, filename: 'example.csv', type: 'text/csv', disposition: 'attachment' ) end # @path [POST] /ticket_articles/import # # @summary Starts import. # @notes The requester have 'admin' permissions to be create a new import. # @example curl -u #{login}:#{password} -F 'file=@/path/to/file/ticket_articles.csv' 'https://your.zammad/api/v1/ticket_articles/import?try=true' # @example curl -u #{login}:#{password} -F 'file=@/path/to/file/ticket_articles.csv' 'https://your.zammad/api/v1/ticket_articles/import' # # @response_message 201 Import started. # @response_message 403 Forbidden / Invalid session. def import_start if Setting.get('import_mode') != true raise __('Tickets can only be imported if system is in import mode.') end string = params[:data] if string.blank? && params[:file].present? string = params[:file].read.force_encoding('utf-8') end raise Exceptions::UnprocessableEntity, __('No source data submitted!') if string.blank? result = Ticket::Article.csv_import( string: string, parse_params: { col_sep: ';', }, try: params[:try], ) render json: result, status: :ok end def retry_security_process article = Ticket::Article.find(params[:id]) authorize!(article, :update?) result = SecureMailing.retry(article) render json: result end def retry_whatsapp_attachment_download article = Ticket::Article.find(params[:id]) authorize!(article, :update?) retry_media = Whatsapp::Retry::Media.new(article:) retry_media.process render json: {}, status: :ok rescue => e logger.error e render json: { error: __('The retried attachment download failed.') }, status: :unprocessable_entity end private def render_calendar_preview render json: Service::Calendar::IcsFile::Parse.new(current_user:).execute(file: download_file), status: :ok rescue => e logger.error e render json: { error: __('The preview cannot be generated. The format is corrupted or not supported.') }, status: :unprocessable_entity end end