Class: Node

Overview

A Node is the root class of all elements in the zena application. Class inheritance diagram:

FIXME: some parts are not correct (Partial, Task, Request, Milestone). Either correct this tree or add these classes. Node (manages access and publication cycle)

|
+-- Page (web pages)
|     |
|     +--- Project (has it's own project_id. Can contain notes, collaborators, etc)
|     |
|     +--- Section (has it's own section_id = group of pages)
|            |
|            +--- Skin (theme: contains css, templates, etc)
|
+--- Document
|      |
|      +--- Image
|      |
|      +--- TextDocument       (for css, scripts)
|             |
|             +--- Partial     (uses the zafu templating language)
|                    |
|                    +--- Template  (entry for rendering)
|
+-- Note (date related information, event)
      |
      +--- Post (blog entry)

Properties

The Version class stores the node’s properties (attributes). You need to declare the attributes either in the virtual class or as a Role attached to an existing class in order to use them.

Attributes

Each node uses the following basic attributes:

Base attributes:

zip

unique id (incremented in each site’s scope).

_id

cached title (used to identify nodes in DB: not used in Zena)

site_id

site to which this node belongs to.

parent_id

parent node (every node except root is inserted in a unique place through this attribute).

user_id

creator of the node.

ref_lang

original node language.

created_at

creation date.

updated_at

modification date.

log_at

announcement date.

event_at

event date.

custom_base

boolean value. When set to true, the node’s url becomes it’s fullpath. All it descendants will use this node’s fullpath as their base url. See below for an example.

inherit

inheritance mode (0=custom, 1=inherit, -1=private).

Attributes inherited from the parent:

section_id

reference project (cannot be overwritten even if inheritance mode is custom).

rgroup_id

id of the readers group.

wgroup_id

id of the writers group.

dgroup_id

id of the publishers group.

skin_id

Skin to use when rendering the page (‘theme’).

Attributes used internally:

publish_from

earliest publication date from all published versions.

kpath

inheritance hierarchy. For example an Image has ‘NPDI’ (Node, Page, Document, Image), a Letter would have ‘NNTL’ (Node, Note, Task. Letter). This is used to optimize sql queries.

fullpath

cached full path made of ancestors’ zip (<gdparent zip>/<parent zip>/<self zip>).

basepath

cached base path (the base path is used to build the url depending on the ‘custom_base’ flag).

Node url

A node’s url is made of it’s class and zip. For the examples below, this is our site tree:

root
  |
  +--- projects (Page)
          |
          +--- worldTour (Project)
          |      |
          |      +--- photos (Page)
          |
          +--- music (Project)

The worldTour project’s url would look like:

/en/project21.html

The ‘photos’ url would be:

/en/page23.html

When custom base is set (only for descendants of Page), worldTour url becomes its fullpath:

/en/projects/worldTour

and the ‘photos’ url is now in the worldTour project’s basepath:

/en/projects/worldTour/page23.html

Setting ‘custom_base’ on a node should be done with caution as the node’s zip is on longer in the url and when you move the node around, there is no way to find the new location from the old url. Custom_base should therefore only be used for nodes that are not going to move.

Direct Known Subclasses

Document, Note, Page

Constant Summary collapse

VERSION_ATTRIBUTES =

List of version attributes that should be accessed as proxies ‘v_lang’, ‘v_status’, etc

%w{status lang publish_from backup}
Caster =
::ActiveRecord::ConnectionAdapters::Column
@@native_node_classes =
{'N' => self}
@@native_node_classes_by_name =
{'Node' => self}
@@unhandled_children =
[]

Constants included from Zena::Use::Workflow

Zena::Use::Workflow::WORKFLOW_ATTRIBUTES

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Zena::Use::Search::NodeClassMethods

match_query, search_index, search_records, search_text

Methods included from Zena::Acts::Secure

secure_scope, secure_write_scope, visitor=

Methods included from Zena::Acts::Secure::SecureResult

#construct_id_map, #secure_result

Methods included from Zena::Acts::SecureNode

acts_as_secure_node

Methods included from Zena::Use::Dates::ModelMethods

included

Methods included from Zena::Use::Dates::Common

#format_date

Methods included from Zena::Use::QueryNode::ModelMethods

#db_attr, #find, included, #safe_first, #start_node_zip

Methods included from Zena::Use::ScopeIndex::ModelMethods

included, #rebuild_index_with_scope_index!, #rebuild_scope_index!, #scope_index, scope_index_proc

Methods included from Zena::Use::Relations::ModelMethods

#add_link, included, #l_comment, #l_comment=, #l_date, #l_date=, #l_status, #l_status=, #link_id, #link_id=, #linked_node, #linked_node=, #rel, #rel=, #rel_attributes=, #relation_alias, #relation_links, #relation_proxy, #relation_proxy_from_link, #relations_for_form, #remove_link

Methods included from Zena::Use::Fulltext::ModelMethods

included, #rebuild_index_for_version_with_fulltext

Methods included from Zena::Use::PropEval::ModelMethods

included, #merge_prop_eval, #need_set__id, #rebuild_index_for_version_with_prop_eval, #set__id

Methods included from Zena::Use::VersionHash::ModelMethods

#rebuild_vhash, #v_public?, #version, #version_id, #vhash, #visible_versions

Methods included from Zena::Acts::Serializable::ModelMethods

#all_link_ids, #default_serialization_options, #export_ids, #export_properties, included, #to_xml

Methods included from Zena::Use::Ancestry::ModelMethods

#ancestors, #basepath, #fullpath_as_title, included, #is_ancestor?, #pseudo_id, #short_path, #z_ancestors

Methods included from Zena::Use::Workflow

#apply, #apply_with_callbacks, #auto_publish?, #can_apply?, #can_destroy_version?, #can_edit?, #can_propose?, #can_publish?, #can_refuse?, #can_remove?, #can_unpublish?, #can_update?, #destroy_version, #edit_content!, #get_publish_from, included, #propose, #publish, #redit, #refuse, #remove, #save_without_clone, #set_current_transition, #traductions, #transition_allowed?, #transition_for, #unpublish, #update_attributes, #update_attributes_without_clone, #would_change_original?

Methods included from Zena::Use::NestedAttributesAlias::ModelMethods

#attributes_with_nested_alias=, included, #nested_model_names_for_alias, #resolve_attributes_alias

Methods included from Zena::Acts::Enrollable::ModelMethods

#assigned_roles, #has_role?, included, #zafu_possible_roles

Methods included from Zena::Use::FieldIndex::ModelMethods

included, #property_field_index

Methods included from Zena::Use::MLIndex::ModelMethods

included, #index_reader, #rebuild_index_for_version, #rebuild_index_with_multi_lingual!

Methods included from Zena::Use::Kpath::InstanceMethods

included

Dynamic Method Handling

This class handles dynamic methods through the method_missing method in the class Zena::Use::Relations::ModelMethods

Class Method Details

.allowed_change_to_classesObject

List of classes that a node can change to.



471
472
473
# File 'app/models/node.rb', line 471

def allowed_change_to_classes
  change_to_classes_for_form.map {|k,v| v}
end

.author_procObject

Dynamic resolution of the author class from the user prototype



201
202
203
204
205
206
207
208
209
210
211
# File 'app/models/node.rb', line 201

def self.author_proc
  Proc.new do |h, r, s|
    res = {:method => 'author', :nil => true}
    if prototype = visitor.prototype
      res[:class] = prototype.vclass
    else
      res[:class] = VirtualClass['Node']
    end
    res
  end
end

.auto_create_discussionObject



1000
1001
1002
# File 'app/models/node.rb', line 1000

def auto_create_discussion
  false
end

.cast_to_class(type) ⇒ Object

Return class of cast value.



310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
# File 'app/models/node.rb', line 310

def self.cast_to_class(type)
  case type
    when :string    then String
    when :text      then String
    when :integer   then Number
    when :float     then Number
    when :decimal   then Number
    when :datetime  then Time
    when :timestamp then Time
    when :time      then Time
    when :date      then Time
    when :binary    then String
    when :boolean   then Boolean
    else nil
  end
end

.change_to_classes_for_formObject

Class list to which this class can change to



466
467
468
# File 'app/models/node.rb', line 466

def change_to_classes_for_form
  classes_for_form(:class => 'Node', :without => 'Document')
end

.class_for_relation(rel) ⇒ Object

FIXME: Where is this used ?



882
883
884
885
886
887
888
889
890
891
892
893
# File 'app/models/node.rb', line 882

def class_for_relation(rel)
  case rel
  when 'author'
    User
  when 'traductions'
    Version
  when 'versions'
    Version
  else
    Node
  end
end

.classes_for_form(opts = {}) ⇒ Object

TODO: remove and use VirtualClass.classes_for_form directly



476
477
478
# File 'app/models/node.rb', line 476

def classes_for_form(opts={})
  VirtualClass[self.name].classes_for_form(opts)
end

.create_node(new_attributes, transform = true) ⇒ Object

TODO: cleanup and rename with something indicating the attrs cleanup that this method does.



661
662
663
664
665
666
667
# File 'app/models/node.rb', line 661

def create_node(new_attributes, transform = true)
  node = new_node(new_attributes, transform)
  if node.errors.empty?
    node.save
  end
  node
end

.create_nodes_from_folder(opts) ⇒ Object

Create new nodes from the data in a folder or archive.



670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
# File 'app/models/node.rb', line 670

def create_nodes_from_folder(opts)
  # TODO: all this needs refactoring (and moved into a module).
  # It's probably the messiest part of Zena.
  return [] unless (opts[:folder] || opts[:archive]) && (opts[:parent] || opts[:parent_id])
  scope = self.scoped_methods[0] || {}
  parent_id = opts[:parent_id] || opts[:parent][:id]
  folder    = opts[:folder]
  defaults  = (opts[:defaults] || {}).stringify_keys
  # Initial object class
  klass     = opts[:class] || opts[:klass] || "Page"
  res       = {}

  unless folder
    # Create from archive
    res = nil
    extract_archive(opts[:archive]) do |folder|
      res = create_nodes_from_folder(:folder => folder, :parent_id => parent_id, :defaults => defaults, :klass => klass)
    end

    return res
  end

  entries = Dir.entries(folder).reject { |f| f =~ /^[\.~]|^__/ }.map do |filename|
    [filename, String.from_filename(filename)]
  end.sort

  index  = 0

  while entries[index]
    catch (:next_entry) do
      type = current_obj = sub_folder = document_path = nil
      versions = []
      filename, title = *entries[index]

      path     = File.join(folder, filename)

      if File.stat(path).directory?
        type       = :folder
        sub_folder = path
        attrs      = defaults.dup
        attrs['v_lang'] ||= visitor.lang
      elsif title =~ /^(.+?)(\.\w\w|)(\.\d+|)\.zml$/  # bird.jpg.en.zml
        # node content in yaml
        type      = :node
        title     = "#{$1}#{$4}"
        lang      = $2.blank? ? nil : $2[1..-1]

        # no need for base_node (this is done after all with parse_assets in the controller)
        attrs  = defaults.merge(get_attributes_from_yaml(path))
        attrs['title'] = title
        attrs['v_lang']    = lang || attrs['v_lang'] || visitor.lang
        versions << attrs
      elsif title =~ /^((.+?)\.(.+?))(\.\w\w|)(\.\d+|)$/ # bird.jpg.en
        type      = :document
        title     = $1
        attrs     = defaults.dup
        lang      = $4.blank? ? nil : $4[1..-1]
        attrs['v_lang'] = lang || attrs['v_lang'] || visitor.lang
        attrs['ext']  = $3
        document_path = path
      else
        # Document without extension
        type      = :document
        attrs     = defaults.dup
        lang      = nil
        attrs['v_lang'] = lang || attrs['v_lang'] || visitor.lang
        attrs['ext']  = 'bin'
        document_path = path
      end

      index += 1
      while entries[index] && entries[index][1] =~ /^#{title}(\.\w\w|)(\.\d+|)\.zml$/ # bird.jpg.en.zml
        lang   = $1.blank? ? visitor.lang : $1[1..-1]
        path   = File.join(folder,entries[index][0])

        # we have a zml file. Create a version with this file
        # no need for base_node (this is done after all with parse_assets in the controller)
        attrs = defaults.merge(get_attributes_from_yaml(path))
        attrs['title']  ||= title
        attrs['v_lang'] ||= lang
        versions << attrs

        index += 1
      end

      if versions.empty?
        if type == :folder
          # minimal node for a folder
          attrs['title']    = title
          attrs['v_lang'] ||= lang
          attrs['class']    = klass
          versions << attrs
        elsif type == :document
          # minimal node for a document
          attrs['title']    = title
          attrs['v_lang'] ||= lang
          versions << attrs
        end
      end

      new_object = false
      versions.each do |attrs|
        # FIXME: same lang: remove before update current_obj.remove if current_obj.v_lang == attrs['v_lang'] && current_obj.v_status != Zena::Status::Red
        # FIXME: current_obj.publish if attrs['v_status'].to_i == Zena::Status::Pub
        if type == :document
          attrs['title' ] = attrs['title'].split('.')[0..-2].join('.')
          if document_path
            attrs['ext'] ||= document_path.split('.').last
            # file
            insert_zafu_headings = false
            if opts[:parent_class] == 'Skin'
              if ['html','xhtml'].include?(attrs['ext']) && attrs['title'] == 'index'
                attrs['ext']   = 'zafu'
                attrs['title'] = 'Node'
                attrs['content_type'] = 'text/zafu'
                insert_zafu_headings = true
              elsif attrs['ext'] == 'yml' && attrs['title'] == '_roles'
                # import roles
                # FIXME: security. We should show diff and ask for validation...
                ::Role.import(YAML.load(File.read(document_path)))
                # Continue in outer loop.
                throw :next_entry
              end
            end

            ctype = Zena::EXT_TO_TYPE[attrs['ext']]
            ctype = ctype ? ctype[0] : "application/octet-stream"
            attrs['content_type'] = ctype


            File.open(document_path) do |f|
              file = uploaded_file(f, filename, ctype)
              (class << file; self; end;).class_eval do
                alias o_read read
                define_method(:read) do
                  if insert_zafu_headings
                    o_read.sub(%r{</head>},"  <r:stylesheets/>\n  <r:javascripts/>\n  <r:uses_datebox/>\n</head>")
                  else
                    o_read
                  end
                end
              end
              current_obj = create_or_update_node(attrs.merge(:file => file, :klass => 'Document', :_parent_id => parent_id))
            end
            document_path = nil
          else
            current_obj = create_or_update_node(attrs.merge(:_parent_id => parent_id, :klass => 'Document'))
          end
        else
          # :folder, :node
          current_obj = create_or_update_node(attrs.merge(:_parent_id => parent_id))
        end
        new_object = new_object || current_obj.instance_variable_get(:@new_record_before_save)
      end
      current_obj.instance_variable_set(:@new_record_before_save, new_object)
      current_obj.instance_variable_set(:@versions_count, versions.size)

      res[current_obj[:id].to_i] = current_obj

      res.merge!(create_nodes_from_folder(:folder => sub_folder, :parent_id => current_obj[:id], :defaults => defaults, :parent_class => current_obj.klass)) if sub_folder && !current_obj.new_record?
    end # catch :next_entry
  end
  res
end

.create_or_update_node(new_attributes) ⇒ Object

def attr_public?(attribute)

if attribute.to_s =~ /(.*)_zips?$/
  return true if self.ancestors.include?(Node) && RelationProxy.find_by_role($1.singularize)
end
super

end



576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
# File 'app/models/node.rb', line 576

def create_or_update_node(new_attributes)
  attributes = transform_attributes(new_attributes)

  v_lang = attributes['v_lang']
  if !current_site.lang_list.include?(v_lang)
    attributes['v_lang'] = current_site.lang_list.first
  end

  if zip = attributes.delete('parent_zip')
    if id = secure(Node) { Node.translate_pseudo_id(zip, :id, self) }
      attributes['parent_id'] = id
    else
      node = Node.new
      node.errors.add('parent_id', 'could not be found')
      return node
    end
  end

  unless attributes['title'] && attributes['parent_id']
    node = Node.new
    node.errors.add('title', "can't be blank")     if attributes['title'].blank?
    node.errors.add('parent_id', "can't be blank") if attributes['parent_id'].blank?
    return node
  end

  # FIXME: remove 'with_exclusive_scope' once scopes are clarified and removed from 'secure'
  node = Node.send(:with_exclusive_scope) do
    find_by_parent_title_and_kpath(attributes['parent_id'], attributes['title'], nil)
  end

  if node
    visitor.visit(node) # secure
    # TODO: class ignored (could be used to transform from one class to another...)
    attributes.delete('class')
    attributes.delete('klass')
    updated_date = node.updated_at
    node.update_attributes(attributes)

    if updated_date != node.updated_at
      node[:create_or_update] = 'updated'
    else
      node[:create_or_update] = 'same'
    end
  else
    node = create_node(attributes, false)
    node[:create_or_update] = 'new'
  end

  node
end

.extract_archive(archive) ⇒ Object



835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
# File 'app/models/node.rb', line 835

def extract_archive(archive)
  begin
    n = 0
    # TODO: we could move the tmp folder inside sites/{current_site}/tmp
    folder = File.join(RAILS_ROOT, 'tmp', sprintf('%s.%d.%d', 'import', $$, n))
  end while File.exists?(folder)

  begin
    FileUtils::mkpath(folder)

    if archive.kind_of?(StringIO)
      filename = archive.original_filename
      tempf = Tempfile.new(archive.original_filename)
      File.open(tempf.path, 'wb') { |f| f.syswrite(archive.read) }
      archive = tempf
    else
      filename = archive.original_filename
    end

    # extract file in this temporary folder.
    # FIXME: SECURITY is there a security risk here ?
    # FIXME: not compatible with Windows.
    if filename =~ /\.tgz$/
      `tar -C '#{folder}' -xz < '#{archive.path}'`
    elsif filename =~ /\.tar$/
      `tar -C '#{folder}' -x < '#{archive.path}'`
    elsif filename =~ /\.zip$/
      `unzip -d '#{folder}' '#{archive.path}'`
    elsif filename =~ /(.*)(\.gz|\.z)$/
      `gzip -d '#{archive.path}' -c > '#{folder}/#{$1.gsub("'",'')}'`
    else
      # FIXME: send errors back
      puts "BAD #{archive.inspect}"
    end
    yield folder
  ensure
    FileUtils::rmtree(folder)
  end
end

.find_by_parent_title_and_kpath(parent_id, title, kpath = nil, opts = {}) ⇒ Object



398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
# File 'app/models/node.rb', line 398

def find_by_parent_title_and_kpath(parent_id, title, kpath = nil, opts = {})
  if cond = opts[:conditions]
    cond[0] = Array(cond[0])
  else
    cond = opts[:conditions] = [[]]
  end

  if kpath
    cond[0] << "kpath like ?"
    cond << "#{kpath}%"
  end
  cond[0] << "site_id = ? AND parent_id = ?"
  cond << current_site.id << parent_id

  find_by_title(title, opts)
end

.find_by_title(title, opts = {}) ⇒ Object

Find node by the indexed title.



416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
# File 'app/models/node.rb', line 416

def find_by_title(title, opts = {})
  if cond = opts[:conditions]
    cond[0] = Array(cond[0])
  else
    cond = opts[:conditions] = [[]]
  end

  if opts.delete(:like)
    cond[0] << "id1.value LIKE ?"
  else
    cond[0] << "id1.value = ?"
  end
  cond << title

  cond[0] = cond[0].join(' AND ')

  opts[:joins] = Node.title_join
  opts[:select] = 'nodes.*'

  Node.find(:first, opts)
end

.find_by_zip(zip) ⇒ Object

Raises:

  • (ActiveRecord::RecordNotFound)


875
876
877
878
879
# File 'app/models/node.rb', line 875

def find_by_zip(zip)
  node = find(:first, :conditions=>"zip = #{zip.to_i}")
  raise ActiveRecord::RecordNotFound unless node
  node
end

.find_node_by_pseudo(id, base_node = nil) ⇒ Object

Find a node based on a query shortcut. Used by zazen to create a link for “”::art for example.



526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
# File 'app/models/node.rb', line 526

def find_node_by_pseudo(id, base_node = nil)
  raise Zena::AccessViolation if self.scoped_methods == []
  str = id.to_s
  if str =~ /\A\d+\Z/
    # zip
    find_by_zip(str)
  elsif path = str[/\A\(([^\)]+)\)\Z/,1]
    if path[0..0] == '/'
      path = path[1..-1].split('/').map {|p| String.from_filename(p) }
      find_by_path(path)
    elsif base_node
      # transform ../../foo and 45/32/61/72 ==> 'foo' and 45/32
      path = path.split('/')
      root = base_node.fullpath.split('/')
      while path[0] == '..'
        root.pop
        path.shift
      end

      path = path.map {|p| String.from_filename(p) }

      if base_node.zip == root.last.to_i
        find_by_path(path, base_node.id)
      elsif root.last
        if base = find_by_zip(root.last)
          find_by_path(path, base.id)
        else
          nil
        end
      else
        find_by_path(path)
      end
    else
      # do not use (path) pseudo when there is no base_node (during create_or_update_node for example).
      # FIXME: path pseudo is needed for links... and it should be done here (egg and hen problem)
      nil
    end
  elsif str =~ /\A:?([^\+]+)(\+*)\Z/
    offset = $2.to_s.size
    Node.search_records($1.gsub('-',' '), :offset => offset, :limit => 1).first
  end
end

.get_attributes_from_yaml(filepath, base_node = nil) ⇒ Object



980
981
982
983
984
# File 'app/models/node.rb', line 980

def get_attributes_from_yaml(filepath, base_node = nil)
  attributes = YAML::load( File.read( filepath ) )
  attributes.delete(:_parent_id)
  transform_attributes(attributes, base_node)
end

.get_class(rel, opts = {}) ⇒ Object

Return class or virtual class from name. FIXME: remove once everything can use VirtualClass



492
493
494
495
496
497
498
499
500
501
502
# File 'app/models/node.rb', line 492

def get_class(rel, opts={})
  # mushroom_types ==> MushroomType
  class_name = rel =~ /\A[a-z]/ ? rel.singularize.camelize : rel
  vclass = VirtualClass[class_name]
  if vclass && opts[:create] && vclass.id
    # TODO: how do we deal with real class ? (Currently = pass).
    visitor.group_ids.include?(vclass.create_group_id) ? vclass : nil
  else
    vclass
  end
end

.get_role(rel) ⇒ Object

Find a role by name.



505
506
507
508
509
# File 'app/models/node.rb', line 505

def get_role(rel)
  # mushroom_types ==> MushroomType
  role_name = rel =~ /\A[a-z]/ ? rel.singularize.camelize : rel
  Role.first(:conditions => ['name = ? AND site_id = ?', role_name, current_site.id])
end

.inherited(child) ⇒ Object



390
391
392
393
394
395
396
# File 'app/models/node.rb', line 390

def inherited(child)
  super
  unless child.name.blank?
    # Do not register anonymous classes created during Zafu compilation
    @@unhandled_children << child
  end
end

.inspectObject

Seeing all the columns of the Node class on every inspect does not help at all.



996
997
998
# File 'app/models/node.rb', line 996

def inspect
  to_s
end

.kpath_match?(kpath) ⇒ Boolean

check inheritance chain through kpath

Returns:

  • (Boolean)


461
462
463
# File 'app/models/node.rb', line 461

def kpath_match?(kpath)
  self.kpath =~ /^#{kpath}/
end

.kpaths_for_form(opts = {}) ⇒ Object

FIXME: how to make sure all sub-classes of Node are loaded before this is called ? TODO: move into helper



482
483
484
485
486
487
488
# File 'app/models/node.rb', line 482

def kpaths_for_form(opts={})
  VirtualClass.all_classes(opts).map do |vclass|
    # white spaces are insecable spaces (not ' ')
    a, b = vclass.kpath, vclass.name
    [a[1..-1].gsub(/./,'  ') + b, a]
  end
end

.load_unhandled_childrenObject



450
451
452
453
454
455
456
457
458
# File 'app/models/node.rb', line 450

def load_unhandled_children
  # this is to make sure subclasses are loaded before the first call
  # TODO: find a better way to make sure they are all loaded
  [Note,Page,Project,Section,Document,Image,TextDocument,Skin,Template]
  while child = @@unhandled_children.pop
    @@native_node_classes[child.kpath] = child
    @@native_node_classes_by_name[child.name] = child
  end
end

.make_schemaObject

We want to use a Role as schema for properties defined in the real_class instead of Property::Schema.



127
128
129
130
131
132
133
134
135
# File 'app/models/node.rb', line 127

def self.make_schema
  ::Role.new(:name => name).tap do |role|
    role.kpath = self.kpath
    # Enable property method definitions.
    role.klass      = self
    # Used for property inheritance.
    role.real_class = self
  end
end

.native_classesObject

Return the list of (kpath,subclasses) for the current class.



439
440
441
442
# File 'app/models/node.rb', line 439

def native_classes
  load_unhandled_children
  @@native_node_classes
end

.native_classes_by_nameObject

Return the list of (name,class) for the current class.



445
446
447
448
# File 'app/models/node.rb', line 445

def native_classes_by_name
  load_unhandled_children
  @@native_node_classes_by_name
end

.new(hash = {}, vclass = nil) ⇒ Object Also known as: new_instance



376
377
378
379
380
381
382
# File 'app/models/node.rb', line 376

def new(hash={}, vclass = nil)
  node = super()
  # set virtual_class (acts as schema) before setting attributes
  node.virtual_class = vclass || VirtualClass[self.name]
  node.attributes = hash
  node
end

.new_node(new_attributes, transform = true) ⇒ Object

TODO: cleanup and rename with something indicating the attrs cleanup that this method does.



628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
# File 'app/models/node.rb', line 628

def new_node(new_attributes, transform = true)
  attributes = transform ? transform_attributes(new_attributes) : new_attributes

  klass_name = attributes.delete('class') || attributes.delete('klass')
  klass_name ||= attributes['file'] ? 'Document' : 'Page'
  
  if klass_name.kind_of?(VirtualClass) || klass_name.kind_of?(Class)
    klass = klass_name
  else
    unless klass = get_class(klass_name, :create => true)
      node = Node.new
      node.instance_eval { @attributes.merge!(attributes) }
      node.errors.add('klass', 'invalid')
      # This is to show the klass in the form seizure
      node.instance_variable_set(:@klass, klass_name.to_s)
      def node.klass; @klass; end
      return node
    end
  end
  
  if attributes['file'] && !(klass.kpath =~ %r{^ND})
    klass = VirtualClass['Document']
  end
  
  if klass.kind_of?(VirtualClass)
    node = secure(klass.real_class) { klass.new_instance(attributes) }
  else
    node = secure(klass) { klass.new_instance(attributes) }
  end
  node
end

.plural_relation?(rel) ⇒ Boolean

Returns:

  • (Boolean)


895
896
897
898
899
900
901
902
903
904
905
906
# File 'app/models/node.rb', line 895

def plural_relation?(rel)
  rel = rel.split(/\s/).first
  if ['root', 'parent', 'self', 'children', 'documents_only', 'all_pages'].include?(rel) || Node.get_class(rel)
    rel.pluralize == rel
  elsif rel =~ /\A\d+\Z/
    false
  else
    relation = RelationProxy.find_by_role(rel.singularize)
    return rel =~ /s$/ unless relation
    relation.target_role == rel.singularize ? !relation.target_unique : !relation.source_unique
  end
end

.transform_attributes(new_attributes, base_node = nil, change_timezone = true, is_link = false) ⇒ Object

Translate attributes from the visitor’s reference to the application. This method translates dates, zazen shortcuts and zips and returns a stringified hash.



910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
# File 'app/models/node.rb', line 910

def transform_attributes(new_attributes, base_node = nil, change_timezone = true, is_link = false)
  res = {}
  res['parent_id'] = new_attributes[:_parent_id] if new_attributes[:_parent_id] # real id set inside zena.

  copy_node = new_attributes.delete(:_copy)
  attributes = new_attributes.stringify_keys
  
  if copy_node || attributes['copy_id']
    if !copy_node && attributes['copy_id'].blank?
      attributes = Node.new.replace_attributes_in_values(attributes)
    else
      copy_node ||= Node.find_by_zip(attributes.delete('copy_id'))
      attributes = copy_node.replace_attributes_in_values(attributes)
    end
  end

  if !res['parent_id'] && p = attributes['parent_id']
    res['parent_zip'] = p unless p.blank?
  end

  attributes.each do |key, value|
    next if ['parent_id', 'parent_zip', '_parent_id'].include?(key)

    if %w{rgroup_id wgroup_id dgroup_id}.include?(key)
      res[key] = Group.translate_pseudo_id(value, :id) || value
    elsif %w{rgroup wgroup dgroup}.include?(key)
      res["#{key}_id"] = Group.translate_pseudo_id(value, :id) || value
    elsif %w{user_id}.include?(key)
      res[key] = User.translate_pseudo_id(value, :id) || value
    elsif %w{link_id}.include?(key)
      # Link id, not translated
      res[key] = value
    elsif %w{id create_at updated_at}.include?(key)
      # ignore (can be present in xml)
    elsif %w{log_at event_at v_publish_from}.include?(key) || (is_link && %w{date}.include?(key))
      # FIXME: !!! We need to fix timezone parsing in dates depending on the Schema used. This means
      # that we probably need to do this at the property level (during write).
      if value.kind_of?(Time)
        res[key] = value
      elsif value
        # parse date
        if key == 'date'
          # TODO: this is a temporary hack because date in links do not support timezones/formats properly
          res[key] = value.to_utc("%Y-%m-%d %H:%M:%S")
        else
          res[key] = value.to_utc(_(Zena::Use::Dates::DATETIME), change_timezone ? visitor.tz : nil)
        end
      end
    elsif key =~ /^(\w+)_id$/
      res["#{$1}_zip"] = value
    elsif key =~ /^(\w+)_ids$/
      res["#{$1}_zips"] = value.kind_of?(Array) ? value : value.split(',').map(&:strip)
    elsif key == 'v_status' || key == 'file'
      res[key] = value unless value.blank?
    elsif value.kind_of?(Hash)
      res[key] = transform_attributes(value, base_node, change_timezone, %w{link rel rel_attributes}.include?(key) || is_link)
    else
      # translate zazen
      if value.kind_of?(String)
        # FIXME: ignore if 'text' of a TextDocument...
        res[key] = ZazenParser.new(value,:helper=>self).render(:translate_ids=>:zip, :node=>base_node)
      else
        res[key] = value
      end
    end
  end

  res
end

.translate_pseudo_id(id, sym = :id, base_node = nil) ⇒ Object

Find a node’s attribute based on a pseudo (id or path). Used by zazen to create a link for “”::art or “”:(people/ant) for example.



512
513
514
515
516
517
518
519
520
521
522
523
# File 'app/models/node.rb', line 512

def translate_pseudo_id(id, sym = :id, base_node = nil)
  if id.to_s =~ /\A(-?)(\d+)\Z/
    # zip
    # FIXME: this is not secure
    res = Zena::Db.fetch_attribute("SELECT #{sym} FROM nodes WHERE site_id = #{current_site[:id]} AND zip = '#{$2}'")
    res ? ($1.blank? ? res.to_i : -res.to_i) : nil
  elsif node = find_node_by_pseudo(id,base_node)
    node[sym]
  else
    nil
  end
end

.zafu_attribute(node, attribute) ⇒ Object

Return a safe string to access node attributes in compiled templates and compiled sql.



987
988
989
990
991
992
993
# File 'app/models/node.rb', line 987

def zafu_attribute(node, attribute)
  if node.kind_of?(String)
    raise Exception.new("You should use safe_method_type...")
  else
    node.safe_read(attribute)
  end
end

Instance Method Details

#all_relationsObject

Used by zafu to find the search score def score

self[:score]

end



1132
1133
1134
# File 'app/models/node.rb', line 1132

def all_relations
  @all_relations ||= self.vclass.all_relations(self)
end

#archiveObject

create a ‘tgz’ archive with node content and children, returning temporary file path



1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
# File 'app/models/node.rb', line 1390

def archive
  n = 0
  while true
    folder_path = File.join(RAILS_ROOT, 'tmp', sprintf('%s.%d.%d', 'archive', $$, n))
    n += 1
    break unless File.exists?(folder_path)
  end

  begin
    FileUtils::mkpath(folder_path)
    export_to_folder(folder_path)
    tempf = Tempfile.new(title.to_filename)
    `cd #{folder_path.inspect}; tar czf #{tempf.path.inspect} *`
  ensure
    FileUtils::rmtree(folder_path)
  end
  tempf
end

#asset_path(asset_filename) ⇒ Object

Return save path for an asset (element produced by text like a png file from LateX)



1104
1105
1106
1107
1108
# File 'app/models/node.rb', line 1104

def asset_path(asset_filename)
  # It would be nice to move this outside 'self[:id]' so that the same asset can
  # be used by many pages... But then, how do we expire unused assets ?
  "#{SITES_ROOT}#{site.data_path}/asset/#{self[:id]}/#{asset_filename}"
end

#authObject



1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
# File 'app/models/node.rb', line 1504

def auth
  @auth ||= if l = auth_user
    h = StringHash.new
    [:login, :lang, :profile].each do |k|
      h[k] = auth_user.send(k)
    end
    h
  else
    {}
  end
end

#auth=(params) ⇒ Object



1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
# File 'app/models/node.rb', line 1485

def auth=(params)
  return unless visitor.is_manager?
  if !users = auth_users
    # Create user
    user = secure(User) { User.new }
    users = @auth_users = [user]
    @auth_user = user
  end
  users.each do |user|
    user.attributes = params
  end
  @auth = nil
end

#auth_userObject

Find first user using this node as contact node



1517
1518
1519
1520
1521
1522
# File 'app/models/node.rb', line 1517

def auth_user
  @auth_user ||= begin
    l = auth_users
    l && l.first
  end
end

#auth_usersObject

Find all users using this node as contact node.



1500
1501
1502
# File 'app/models/node.rb', line 1500

def auth_users
  @auth_users ||= new_record? ? nil : secure(User) { User.all(:conditions => {:node_id => self.id}) }
end

#authorObject

ACCESSORS



1207
1208
1209
# File 'app/models/node.rb', line 1207

def author
  user.node
end

#can_auto_create_discussion?Boolean

Automatically create a discussion if any of the following conditions are met:

  • there already exists an outside, open discussion for another language

  • the node is not published (creates an internal discussion)

  • the user has drive access to the node

Returns:

  • (Boolean)


1309
1310
1311
1312
1313
1314
# File 'app/models/node.rb', line 1309

def can_auto_create_discussion?
  can_drive? ||
  (v_status != Zena::Status::Pub) ||
  Discussion.find(:first, :conditions=>[ "node_id = ? AND inside = ? AND open = ?",
                           self[:id], false, true ])
end

#can_comment?Boolean

Return true if it is allowed to add comments to the node in the current context

Returns:

  • (Boolean)


1357
1358
1359
# File 'app/models/node.rb', line 1357

def can_comment?
  visitor.commentator? && discussion && discussion.open?
end

#commentsObject

Comments for the current context. Returns nil when there is no discussion.



1338
1339
1340
1341
1342
1343
1344
1345
# File 'app/models/node.rb', line 1338

def comments
  if discussion
    res = discussion.comments(:with_prop=>can_drive?)
    res == [] ? nil : res
  else
    nil
  end
end

#comments_countObject

TODO: remove, replace by relation proxy: proxy.count…



1348
1349
1350
1351
1352
1353
1354
# File 'app/models/node.rb', line 1348

def comments_count
  if discussion
    discussion.comments_count(:with_prop=>can_drive?)
  else
    0
  end
end

#content_langObject

Return the code language used for syntax highlighting.



1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
# File 'app/models/node.rb', line 1111

def content_lang
  ctype = prop['content_type']
  if ctype =~ /^text\/(.*)/
    case $1
    when 'x-ruby-script'
      'ruby'
    when 'html', 'zafu'
      'zafu'
    else
      $1
    end
  else
    nil
  end
end

#dataObject

Find all data entries linked to the current node



1223
1224
1225
1226
# File 'app/models/node.rb', line 1223

def data
  list = DataEntry.find(:all, :conditions => "node_a_id = #{id} OR node_b_id = #{id} OR node_c_id = #{id} OR node_d_id = #{id}", :order => 'date ASC,created_at ASC')
  list == [] ? nil : list
end

#discussionObject

Find the discussion for the current context (v_status and v_lang). This automatically creates a new #Discussion if there is no closed or open discussion for the current lang and Node#can_auto_create_discussion? is true



1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
# File 'app/models/node.rb', line 1293

def discussion
  return @discussion if defined?(@discussion)

  @discussion = Discussion.find(:first, :conditions=>[ "node_id = ? AND inside = ? AND lang = ?",
    self[:id], v_status != Zena::Status::Pub, v_lang ], :order=>'id DESC') ||
    if can_auto_create_discussion?
      Discussion.new(:node_id=>self[:id], :lang=>v_lang, :inside=>(v_status != Zena::Status::Pub))
    else
      nil
    end
end

#dyn_attribute_keysObject



1062
1063
1064
# File 'app/models/node.rb', line 1062

def dyn_attribute_keys
  (version.dyn.keys + (virtual_class ? virtual_class.dyn_keys.to_s.split(',').map(&:strip) : [])).uniq.sort
end

#empty?Boolean

Include data entry verification in multiversion’s empty? method.

Returns:

  • (Boolean)


1384
1385
1386
1387
# File 'app/models/node.rb', line 1384

def empty?
  return true if new_record?
  super && 0 == self.class.count_by_sql("SELECT COUNT(*) FROM #{DataEntry.table_name} WHERE node_a_id = #{id} OR node_b_id = #{id} OR node_c_id = #{id} OR node_d_id = #{id}")
end

#export_keysObject

List of attribute keys to export in a zml file.



1458
1459
1460
1461
1462
1463
# File 'app/models/node.rb', line 1458

def export_keys
  {
    :zazen => Hash[*prop.select { |k, v| v.kind_of?(String) }.flatten],
    :dates => Hash[*prop.select { |k, v| v.kind_of?(Time) }.flatten],
  }
end

#export_to_folder(path) ⇒ Object

export node content and children into a folder



1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
# File 'app/models/node.rb', line 1410

def export_to_folder(path)
  children = secure(Node) { Node.find(:all, :conditions=>['parent_id = ?', self[:id] ]) }

  if kind_of?(Document) && (kind_of?(TextDocument) || text.blank? || text == "!#{zip}!")
    # skip zml
    # TODO: this should better check that version content is really useless
  elsif text.blank? && klass == 'Page' && children
    # skip zml
  else
    File.open(File.join(path, title.to_filename + '.zml'), 'wb') do |f|
      f.puts self.to_yaml
    end
  end

  if kind_of?(Document)
    data = kind_of?(TextDocument) ? StringIO.new(text) : file
    File.open(File.join(path, filename), 'wb') { |f| f.syswrite(data.read) }
  end

  if children
    content_folder = File.join(path, title.to_filename)
    if !FileUtils::mkpath(content_folder)
      puts "Problem..."
    end
    children.each do |child|
      child.export_to_folder(content_folder)
    end
  end
end

#find_node_by_pseudo(string, base_node = nil) ⇒ Object

This is needed during ‘unparse_assets’ when the node is it’s own helper



1471
1472
1473
# File 'app/models/node.rb', line 1471

def find_node_by_pseudo(string, base_node = nil)
  secure(Node) { Node.find_node_by_pseudo(string, base_node || self) }
end

#get_discussion_idObject

Return current discussion id (used by query_builder)



1243
1244
1245
# File 'app/models/node.rb', line 1243

def get_discussion_id
  (discussion && !discussion.new_record?) ? discussion[:id] : '0'
end

#get_project_idObject

Return self if the node is a kind of Project. Return project_id otherwise.



1254
1255
1256
1257
# File 'app/models/node.rb', line 1254

def get_project_id
  # root node is it's own section and project
  self[:parent_id].nil? ? self[:id] : self[:project_id]
end

#get_section_idObject

Return self if the node is a kind of Section. Return section_id otherwise.



1248
1249
1250
1251
# File 'app/models/node.rb', line 1248

def get_section_id
  # root node is it's own section and project
  self[:parent_id].nil? ? self[:id] : self[:section_id]
end

#klassObject



1058
1059
1060
# File 'app/models/node.rb', line 1058

def klass
  @new_klass || @set_klass || vclass.to_s
end

#klass=(str) ⇒ Object



1066
1067
1068
1069
# File 'app/models/node.rb', line 1066

def klass=(str)
  return if str == klass
  @new_klass = str
end

#klass_changed?Boolean

Returns:

  • (Boolean)


116
117
118
# File 'app/models/node.rb', line 116

def klass_changed?
  kpath_changed?
end

#kpath_match?(kpath) ⇒ Boolean

check inheritance chain through kpath

Returns:

  • (Boolean)


1047
1048
1049
# File 'app/models/node.rb', line 1047

def kpath_match?(kpath)
  self.kpath =~ /^#{kpath}/
end

#login_infoObject



1218
1219
1220
# File 'app/models/node.rb', line 1218

def 
  secure(User) { User.find(:first, :conditions => {:node_id => self.id})}
end

#m_authorObject



1320
# File 'app/models/node.rb', line 1320

def m_author; ''; end

#m_author=(str) ⇒ Object



1332
1333
1334
1335
# File 'app/models/node.rb', line 1332

def m_author=(str)
  @add_comment ||= {}
  @add_comment[:author] = str
end

#m_textObject

FIXME: use nested_attributes_alias and try to use native Rails to create the comment comment_attributes=, …



1318
# File 'app/models/node.rb', line 1318

def m_text; ''; end

#m_text=(str) ⇒ Object



1322
1323
1324
1325
# File 'app/models/node.rb', line 1322

def m_text=(str)
  @add_comment ||= {}
  @add_comment[:text] = str
end

#m_titleObject



1319
# File 'app/models/node.rb', line 1319

def m_title; ''; end

#m_title=(str) ⇒ Object



1327
1328
1329
1330
# File 'app/models/node.rb', line 1327

def m_title=(str)
  @add_comment ||= {}
  @add_comment[:title] = str
end

#merge_multi_errors(key, object) ⇒ Object

This is an adaptation of Versions::Multi code to use our special v_ shortcut to access version attributes.



353
354
355
356
357
358
359
# File 'app/models/node.rb', line 353

def merge_multi_errors(key, object)
  if key == 'version'
    super('v', object)
  else
    super
  end
end

#new_child(opts = {}, transform = true) ⇒ Object

Create a child and let him inherit from rwp groups and section_id



1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
# File 'app/models/node.rb', line 1189

def new_child(opts={}, transform = true)
  c = Node.new_node(opts, transform)
  c.parent_id  = self[:id]
  c.instance_variable_set(:@parent, self)

  c.visitor    = visitor

  c.inherit = 1
  c.rgroup_id  = self.rgroup_id
  c.wgroup_id  = self.wgroup_id
  c.dgroup_id  = self.dgroup_id

  c.section_id = self.get_section_id
  c.project_id = self.get_project_id
  c
end

#o_skinObject



1183
# File 'app/models/node.rb', line 1183

alias o_skin skin

#o_userObject



1211
# File 'app/models/node.rb', line 1211

alias o_user user

#parent(is_secure = true) ⇒ Object

Find parent



1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
# File 'app/models/node.rb', line 1137

def parent(is_secure = true)
  # make sure the cache is in sync with 'parent_id' (used during validation)
  if self[:parent_id].nil?
    nil
  elsif is_secure
    # cache parent result (done through secure query)
    return @parent if @parent && @parent[:id] == self[:parent_id]
    @parent = secure(Node) { Node.find(self[:parent_id]) }
  else
    # not secured (inside an exclusive scope)
    return @parent_insecure if @parent_insecure && @parent_insecure[:id] == self[:parent_id]
    @parent_insecure = secure(Node, :secure => false) { Node.find(self[:parent_id]) }
  end
end

#parent_zipObject

Id to zip mapping for parent_id. Used by zafu and forms.



1260
1261
1262
# File 'app/models/node.rb', line 1260

def parent_zip
  @parent_zip || parent.try(:zip)
end

#parent_zip=(zip) ⇒ Object

When setting parent trough controllers, we receive parent_zip=.



1265
1266
1267
# File 'app/models/node.rb', line 1265

def parent_zip=(zip)
  @parent_zip = zip
end

#parse_assets(text, helper, key) ⇒ Object

Parse text content and replace all relative urls (‘../projects/art’) by ids (‘34’)



1092
1093
1094
1095
# File 'app/models/node.rb', line 1092

def parse_assets(text, helper, key)
  # helper is used in textdocuments
  ZazenParser.new(text,:helper=>helper).render(:translate_ids => :zip, :node => self)
end

#parse_keysObject

List of attribute keys to transform (change references, etc).



1466
1467
1468
# File 'app/models/node.rb', line 1466

def parse_keys
  export_keys[:zazen].keys
end

#projectObject

Return self if the current node is a project else find project.



1169
1170
1171
# File 'app/models/node.rb', line 1169

def project
  self.kind_of?(Project) ? self : real_project
end

#project_zipObject

Id to zip mapping for project_id. Used by zafu and forms.



1284
1285
1286
# File 'app/models/node.rb', line 1284

def project_zip
  project[:zip]
end

#rcast(key, type) ⇒ Object

Read with cast to an appropriate instance. This is used along with custom select in QueryBuilder queries.



329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
# File 'app/models/node.rb', line 329

def rcast(key, type)
  @rcast_cache ||= {}
  @rcast_cache[key] ||= begin
    value = @attributes[key]
    return nil if value.nil?
    case type
      when :string    then value
      when :text      then value
      when :integer   then value.to_i rescue value ? 1 : 0
      when :float     then value.to_f
      when :decimal   then Caster.value_to_decimal(value)
      when :datetime  then Caster.string_to_time(value)
      when :timestamp then Caster.string_to_time(value)
      when :time      then Caster.string_to_time(value)
      when :date      then Caster.string_to_time(value)
      when :binary    then Caster.binary_to_string(value)
      when :boolean   then Caster.value_to_boolean(value)
      else value
    end
  end
end

#real_project(is_secure = true) ⇒ Object

Find real project (Project’s project if node is a Project)



1174
1175
1176
1177
1178
1179
1180
1181
# File 'app/models/node.rb', line 1174

def real_project(is_secure = true)
  return self if self[:parent_id].nil?
  if is_secure
    secure(Project) { Project.find(self[:project_id]) }
  else
    secure(Node, :secure => false) { Project.find(self[:project_id]) }
  end
end

#real_section(is_secure = true) ⇒ Object

Find real section



1158
1159
1160
1161
1162
1163
1164
1165
1166
# File 'app/models/node.rb', line 1158

def real_section(is_secure = true)
  return self if self[:parent_id].nil? # root
  # we cannot use Section to find because the root node behaves like a Section but is a Project.
  if is_secure
    secure(Node) { Node.find(self[:section_id]) }
  else
    secure(Node, :secure => false) { Node.find(self[:section_id]) }
  end
end

#reloadObject

Remove loaded version and properties on reload.



1026
1027
1028
1029
1030
# File 'app/models/node.rb', line 1026

def reload
  @version    = nil
  @properties = nil
  super
end

#replace_attributes_in_values(hash) ⇒ Object

Replace [id], [title], etc in attributes values



1084
1085
1086
1087
1088
# File 'app/models/node.rb', line 1084

def replace_attributes_in_values(hash)
  hash.each do |k,v|
    hash[k] = safe_eval_string(v)
  end
end

#safe_method_type(signature, receiver = nil) ⇒ Object

We use the virtual_class as proxy for method type resolution. def safe_eval(code)

eval RubyLess.translate(schema, code)

end



144
145
146
# File 'app/models/node.rb', line 144

def safe_method_type(signature, receiver = nil)
  schema.safe_method_type(signature, receiver)
end

#safe_send(method) ⇒ Object

Safe dynamic method dispatching when the method is not known during compile time. Currently this only works for methods without arguments.



1479
1480
1481
1482
1483
# File 'app/models/node.rb', line 1479

def safe_send(method)
  return nil unless type = virtual_class.safe_method_type([method])
  res = eval(type[:method])
  res ? res.to_s : nil
end

#sectionObject

Return self if the current node is a section else find section.



1153
1154
1155
# File 'app/models/node.rb', line 1153

def section
  self.kind_of?(Section) ? self : real_section
end

#section_zipObject

Id to zip mapping for section_id. Used by zafu and forms.



1279
1280
1281
# File 'app/models/node.rb', line 1279

def section_zip
  section[:zip]
end

#skinObject



1184
1185
1186
# File 'app/models/node.rb', line 1184

def skin
  @skin ||= secure(Skin) { o_skin }
end

#skin_zipObject



1274
1275
1276
# File 'app/models/node.rb', line 1274

def skin_zip
  @skin_zip || skin.try(:zip)
end

#skin_zip=(zip) ⇒ Object

When setting skin trough controllers, we receive skin_zip=.



1270
1271
1272
# File 'app/models/node.rb', line 1270

def skin_zip=(zip)
  @skin_zip = zip.to_i
end

#sweep_cache(filter = nil) ⇒ Object



1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
# File 'app/models/node.rb', line 1361

def sweep_cache(filter = nil)
  return true if current_site.being_created?

  # Clear element cache
  # Partial cache not used
  # Cache.sweep(:visitor_id=>self[:user_id], :visitor_groups=>[rgroup_id, wgroup_id, dgroup_id], :kpath=>self.vclass.kpath)

  # Clear full result cache

  # we want to be sure to find the project and parent, even if the visitor does not have an
  # access to these elements.
  # FIXME: use self + modified relations instead of parent/project
  [self, self.real_project(false), self.real_section(false), self.parent(false)].compact.uniq.each do |obj|
    # destroy all pages in project, parent and section !
    CachedPage.expire_with(obj, filter)
  end

  # clear assets
  FileUtils::rmtree(asset_path(''))
  true
end

#to_yamlObject

export node as a hash



1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
# File 'app/models/node.rb', line 1443

def to_yaml
  hash = {}
  export_keys[:zazen].each do |k, v|
    hash[k] = unparse_assets(v, self, k)
  end

  export_keys[:dates].each do |k, v|
    hash[k] = visitor.tz.utc_to_local(v).strftime("%Y-%m-%d %H:%M:%S")
  end
  
  hash.merge!('class' => self.klass, 'position' => self.position)
  hash.to_yaml
end

#unparse_assets(text, helper, key) ⇒ Object

Parse text and replace ids ‘!30!’ by their pseudo path ‘!(img/bird)!’ key is used in TextDocument overloaded method.



1099
1100
1101
# File 'app/models/node.rb', line 1099

def unparse_assets(text, helper, key)
  ZazenParser.new(text,:helper=>helper).render(:translate_ids => :relative_path, :node=>self)
end

#update_attributes_with_transformation(new_attributes, change_timezone = true) ⇒ Object

Update a node’s attributes, transforming the attributes first from the visitor’s context to Node context.



1079
1080
1081
# File 'app/models/node.rb', line 1079

def update_attributes_with_transformation(new_attributes, change_timezone = true)
  update_attributes(secure(Node) {Node.transform_attributes(new_attributes, self, change_timezone)})
end

#userObject

TODO: why do we need secure here ?



1214
1215
1216
# File 'app/models/node.rb', line 1214

def user
  secure!(User) { o_user }
end

#user_zipObject

Id to zip mapping for user_id. Used by zafu and forms.



1289
# File 'app/models/node.rb', line 1289

def user_zip; self[:user_id]; end

#v_numberObject



303
304
305
# File 'app/models/node.rb', line 303

def v_number
  version.number
end

#vclassObject

virtual class FIXME: alias vclass to virtual_class alias vclass virtual_class



1054
1055
1056
# File 'app/models/node.rb', line 1054

def vclass
  virtual_class || self.class
end

#versions_with_secure(*args) ⇒ Object

TODO: remove when :inverse_of works.



1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
# File 'app/models/node.rb', line 1033

def versions_with_secure(*args)
  proxy = versions_without_secure(*args)
  if frozen?
    proxy = []
  elsif proxy.loaded?
    proxy.each do |v|
      v.node = self
    end
  end
  proxy
end

#virtual_classObject Also known as: schema



108
109
110
111
112
113
114
# File 'app/models/node.rb', line 108

def virtual_class
  @virtual_class ||= if self[:vclass_id]
    VirtualClass.find_by_id(self[:vclass_id])
  else
    VirtualClass.find_by_name(self.class.name)
  end
end

#virtual_class=(vclass) ⇒ Object



120
121
122
123
124
# File 'app/models/node.rb', line 120

def virtual_class=(vclass)
  @virtual_class = vclass
  self[:vclass_id] = vclass.id
  self[:kpath] = vclass.kpath
end

#vkind_of?(klass) ⇒ Boolean

include virtual classes to check inheritance chain

Returns:

  • (Boolean)


1072
1073
1074
1075
1076
# File 'app/models/node.rb', line 1072

def vkind_of?(klass)
  if virt = VirtualClass[klass.to_s]
    kpath_match?(virt.kpath)
  end
end

#zafu_eval(str, opts = {}) ⇒ Object

Enable dynamic property evaluation



1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
# File 'app/models/node.rb', line 1010

def zafu_eval(str, opts = {})
  value = safe_eval(str)
  if value.kind_of?(String)
    value
  elsif value.kind_of?(Time)
    format_date(value, opts)
  elsif value.kind_of?(Array)
    value.join(',')
  else
    value.to_s
  end
rescue RubyLess::Error
  nil
end

#zafu_versionsObject



1005
1006
1007
# File 'app/models/node.rb', line 1005

def zafu_versions
  versions.all(:order => 'number desc')
end